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
17 static unsigned int timeout_ms = 3000;
18 static int interrupted, bad = 1, h1;
19 static lws_state_notify_link_t nl;
20 static size_t hugeurl_size = 4000;
21 static char *hugeurl, *check;
22
23 #if !defined(LWS_SS_USE_SSPC)
24 static 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
142 typedef 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
154 static 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
163 static signed char
lws_httpbin_json_cb(struct lejp_ctx * ctx,char reason)164 lws_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
201 static lws_ss_state_return_t
myss_rx(void * userobj,const uint8_t * buf,size_t len,int flags)202 myss_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
251 static lws_ss_state_return_t
myss_state(void * userobj,void * sh,lws_ss_constate_t state,lws_ss_tx_ordinal_t ack)252 myss_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
312 static 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
321 static int
app_system_state_nf(lws_state_manager_t * mgr,lws_state_notify_link_t * link,int current,int target)322 app_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
349 static lws_state_notify_link_t * const app_notifier_list[] = {
350 &nl, NULL
351 };
352
353 static void
sigint_handler(int sig)354 sigint_handler(int sig)
355 {
356 interrupted = 1;
357 }
358
main(int argc,const char ** argv)359 int 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