1 /*
2 * lws-minimal-secure-streams-smd
3 *
4 * Written in 2010-2020 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 demonstrates a minimal http client using secure streams to access the
11 * SMD api.
12 */
13
14 #include <libwebsockets.h>
15 #include <string.h>
16 #include <signal.h>
17
18 static int interrupted, bad = 1, count_p1, count_p2, count_tx, expected = 0;
19 static unsigned int how_many_msg = 100, usec_interval = 1000;
20 static lws_sorted_usec_list_t sul_timeout;
21
22 /*
23 * If the -proxy app is fulfilling our connection, then we don't need to have
24 * the policy in the client.
25 *
26 * When we build with LWS_SS_USE_SSPC, the apis hook up to a proxy process over
27 * a Unix Domain Socket. To test that, you need to separately run the
28 * ./lws-minimal-secure-streams-proxy test app on the same machine.
29 */
30
31 #if !defined(LWS_SS_USE_SSPC)
32 static const char * const default_ss_policy =
33 "{"
34 "\"schema-version\":1,"
35 "\"s\": ["
36 "{"
37 /*
38 * "captive_portal_detect" describes
39 * what to do in order to check if the path to
40 * the Internet is being interrupted by a
41 * captive portal. If there's a larger policy
42 * fetched from elsewhere, it should also include
43 * this since it needs to be done at least after
44 * every DHCP acquisition
45 */
46 "\"captive_portal_detect\": {"
47 "\"endpoint\": \"connectivitycheck.android.com\","
48 "\"http_url\": \"generate_204\","
49 "\"port\": 80,"
50 "\"protocol\": \"h1\","
51 "\"http_method\": \"GET\","
52 "\"opportunistic\": true,"
53 "\"http_expect\": 204,"
54 "\"http_fail_redirect\": true"
55 "}"
56 "}"
57 "]"
58 "}"
59 ;
60
61 #endif
62
63 typedef struct myss {
64 struct lws_ss_handle *ss;
65 void *opaque_data;
66 /* ... application specific state ... */
67 lws_sorted_usec_list_t sul;
68 char alternate;
69 } myss_t;
70
71
72 /* secure streams payload interface */
73
74 static lws_ss_state_return_t
myss_rx(void * userobj,const uint8_t * buf,size_t len,int flags)75 myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
76 {
77 /*
78 * Call the helper to translate into a real smd message and forward to
79 * this context / process smd participants... except us, since we
80 * definitely already received it
81 */
82
83 if (lws_smd_ss_rx_forward(userobj, buf, len))
84 lwsl_warn("%s: forward failed\n", __func__);
85
86 count_p1++;
87
88 return LWSSSSRET_OK;
89 }
90
91 static void
sul_tx_periodic_cb(lws_sorted_usec_list_t * sul)92 sul_tx_periodic_cb(lws_sorted_usec_list_t *sul)
93 {
94 myss_t *m = lws_container_of(sul, myss_t, sul);
95
96 lwsl_info("%s: requesting TX\n", __func__);
97 if (lws_ss_request_tx(m->ss))
98 lwsl_info("%s: req failed\n", __func__);
99 }
100
101 static lws_ss_state_return_t
myss_tx(void * userobj,lws_ss_tx_ordinal_t ord,uint8_t * buf,size_t * len,int * flags)102 myss_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len,
103 int *flags)
104 {
105 myss_t *m = (myss_t *)userobj;
106
107 lwsl_info("%s: sending SS smd\n", __func__);
108
109 /*
110 * The SS RX isn't going to see INTERACTION messages, because its class
111 * filter doesn't accept INTERACTION class messages. The direct
112 * participant we also set up for the test will see them though.
113 *
114 * Let's alternate between sending NETWORK class smd messages and
115 * INTERACTION so we can test both rx paths
116 */
117
118 m->alternate++;
119
120 if (m->alternate == 4) {
121 /*
122 * after a few, let's request a CPD check
123 */
124
125 if (lws_smd_ss_msg_printf(lws_ss_tag(m->ss), buf, len, LWSSMDCL_NETWORK,
126 "{\"trigger\": \"cpdcheck\", "
127 "\"src\":\"SS-test\"}"))
128 return LWSSSSRET_TX_DONT_SEND;
129 } else
130 if (lws_smd_ss_msg_printf(lws_ss_tag(m->ss), buf, len,
131 (m->alternate & 1) ? LWSSMDCL_NETWORK :
132 LWSSMDCL_INTERACTION,
133 (m->alternate & 1) ?
134 "{\"class\":\"NETWORK\",\"x\":%d}" :
135 "{\"class\":\"INTERACTION\",\"x\":%d}",
136 count_tx))
137 return LWSSSSRET_TX_DONT_SEND;
138
139 *flags = LWSSS_FLAG_SOM | LWSSS_FLAG_EOM;
140
141 count_tx++;
142
143 lws_sul_schedule(lws_ss_get_context(m->ss), 0, &m->sul,
144 sul_tx_periodic_cb, usec_interval);
145
146 return LWSSSSRET_OK;
147 }
148
149 static lws_ss_state_return_t
myss_state(void * userobj,void * h_src,lws_ss_constate_t state,lws_ss_tx_ordinal_t ack)150 myss_state(void *userobj, void *h_src, lws_ss_constate_t state,
151 lws_ss_tx_ordinal_t ack)
152 {
153 myss_t *m = (myss_t *)userobj;
154
155 lwsl_notice("%s: %s: %s (%d), ord 0x%x\n", __func__, lws_ss_tag(m->ss),
156 lws_ss_state_name((int)state), state, (unsigned int)ack);
157
158 if (state == LWSSSCS_DESTROYING) {
159 lws_sul_cancel(&m->sul);
160 return LWSSSSRET_OK;
161 }
162
163 if (state == LWSSSCS_CONNECTED) {
164 lwsl_notice("%s: CONNECTED\n", __func__);
165 lws_sul_schedule(lws_ss_get_context(m->ss), 0, &m->sul,
166 sul_tx_periodic_cb, 1);
167 return LWSSSSRET_OK;
168 }
169
170 return LWSSSSRET_OK;
171 }
172
173 static const lws_ss_info_t ssi_lws_smd = {
174 .handle_offset = offsetof(myss_t, ss),
175 .opaque_user_data_offset = offsetof(myss_t, opaque_data),
176 .rx = myss_rx,
177 .tx = myss_tx,
178 .state = myss_state,
179 .user_alloc = sizeof(myss_t),
180 .streamtype = LWS_SMD_STREAMTYPENAME,
181 .manual_initial_tx_credit = LWSSMDCL_SYSTEM_STATE |
182 LWSSMDCL_METRICS |
183 LWSSMDCL_NETWORK,
184 };
185
186 /* for comparison, this is a non-SS lws_smd participant */
187
188 static int
direct_smd_cb(void * opaque,lws_smd_class_t _class,lws_usec_t timestamp,void * buf,size_t len)189 direct_smd_cb(void *opaque, lws_smd_class_t _class, lws_usec_t timestamp,
190 void *buf, size_t len)
191 {
192 struct lws_context **pctx = (struct lws_context **)opaque;
193
194 // lwsl_notice("%s: class: 0x%x, ts: %llu\n", __func__, _class,
195 // (unsigned long long)timestamp);
196 // lwsl_hexdump_notice(buf, len);
197
198 count_p2++;
199
200 if (_class != LWSSMDCL_SYSTEM_STATE)
201 return 0;
202
203 if (!lws_json_simple_strcmp(buf, len, "\"state\":", "OPERATIONAL")) {
204
205 #if !defined(LWS_SS_USE_SSPC)
206 /*
207 * Let's trigger a CPD check, just as a test. SS can't see it
208 * anyway since it doesn't listen for NETWORK but the direct /
209 * local participant will see it and the result
210 *
211 * This process doesn't run the smd / captive portal action
212 * when it's a client of the SS proxy. SMD has to be passed
213 * via the SS _lws_smd proxied connection in that case.
214 */
215 (void)lws_smd_msg_printf(*pctx, LWSSMDCL_NETWORK,
216 "{\"trigger\": \"cpdcheck\", \"src\":\"direct-test\"}");
217 #endif
218
219 /*
220 * Create the SS link to lws_smd... notice in ssi_lws_smd
221 * above, we tell this link to use a class filter that excludes
222 * NETWORK messages.
223 */
224
225 if (lws_ss_create(*pctx, 0, &ssi_lws_smd, NULL, NULL, NULL, NULL)) {
226 lwsl_err("%s: failed to create secure stream\n",
227 __func__);
228 interrupted = 1;
229 lws_cancel_service(*pctx);
230 return -1;
231 }
232 }
233
234 return 0;
235 }
236
237
238 static void
sul_timeout_cb(lws_sorted_usec_list_t * sul)239 sul_timeout_cb(lws_sorted_usec_list_t *sul)
240 {
241 lwsl_notice("%s: test finishing\n", __func__);
242 interrupted = 1;
243 }
244
245
246 static void
sigint_handler(int sig)247 sigint_handler(int sig)
248 {
249 interrupted = 1;
250 }
251
252 extern int smd_ss_multi_test(int argc, const char **argv);
253
main(int argc,const char ** argv)254 int main(int argc, const char **argv)
255 {
256 struct lws_context_creation_info info;
257 struct lws_context *context;
258 const char *p;
259
260 signal(SIGINT, sigint_handler);
261
262 memset(&info, 0, sizeof info);
263
264 #if defined(LWS_SS_USE_SSPC)
265 if (lws_cmdline_option(argc, argv, "--multi"))
266 return smd_ss_multi_test(argc, argv);
267 #endif
268
269 lws_cmdline_option_handle_builtin(argc, argv, &info);
270
271 if ((p = lws_cmdline_option(argc, argv, "--count")))
272 how_many_msg = (unsigned int)atol(p);
273
274 if ((p = lws_cmdline_option(argc, argv, "--interval")))
275 usec_interval = (unsigned int)atol(p);
276
277 lwsl_user("LWS Secure Streams SMD test client [-d<verb>]: "
278 "%u msgs at %uus interval\n", how_many_msg, usec_interval);
279
280 info.fd_limit_per_thread = 1 + 6 + 1;
281 info.port = CONTEXT_PORT_NO_LISTEN;
282 #if !defined(LWS_SS_USE_SSPC)
283 info.pss_policies_json = default_ss_policy;
284 #else
285 info.protocols = lws_sspc_protocols;
286 {
287 /* connect to ssproxy via UDS by default, else via
288 * tcp connection to this port */
289 if ((p = lws_cmdline_option(argc, argv, "-p")))
290 info.ss_proxy_port = (uint16_t)atoi(p);
291
292 /* UDS "proxy.ss.lws" in abstract namespace, else this socket
293 * path; when -p given this can specify the network interface
294 * to bind to */
295 if ((p = lws_cmdline_option(argc, argv, "-i")))
296 info.ss_proxy_bind = p;
297
298 /* if -p given, -a specifies the proxy address to connect to */
299 if ((p = lws_cmdline_option(argc, argv, "-a")))
300 info.ss_proxy_address = p;
301 }
302 #endif
303 info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
304 LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
305
306 info.early_smd_cb = direct_smd_cb;
307 info.early_smd_class_filter = 0xffffffff;
308 info.early_smd_opaque = &context;
309
310 /* create the context */
311
312 context = lws_create_context(&info);
313 if (!context) {
314 lwsl_err("lws init failed\n");
315 return 1;
316 }
317
318 #if defined(LWS_SS_USE_SSPC)
319 if (!lws_create_vhost(context, &info)) {
320 lwsl_err("%s: failed to create default vhost\n", __func__);
321 goto bail;
322 }
323 #endif
324
325 /* set up the test timeout */
326
327 lws_sul_schedule(context, 0, &sul_timeout, sul_timeout_cb,
328 (how_many_msg * (usec_interval + 50000)) + LWS_US_PER_SEC);
329
330 /* the event loop */
331
332 while (lws_service(context, 0) >= 0 && !interrupted)
333 ;
334
335 /* compare what happened with what we expect */
336
337 #if defined(LWS_SS_USE_SSPC)
338 /* if SSPC
339 *
340 * - the SS _lws_smd link does not enable INTERACTION class, so doesn't
341 * see these messages (count_p1 is half count_tx)
342 *
343 * - the direct smd participant sees local state, but it doesn't send
344 * any local CPD request, since as a client it doesn't do CPD
345 * directly (count_p2 -= 1 compared to non-SSPC)
346 *
347 * - one CPD trigger is sent on the proxied SS link (countp1 += 1)
348 */
349 if (count_p1 >= 6 && count_p2 >= 11 && count_tx >= 12)
350 #else
351 /* if not SSPC, then we can see direct smd activity */
352 if (count_p1 >= 2 && count_p2 >= 15 && count_tx >= 5)
353 #endif
354 bad = 0;
355
356 lwsl_notice("%d %d %d\n", count_p1, count_p2, count_tx);
357
358 #if defined(LWS_SS_USE_SSPC)
359 bail:
360 #endif
361 lws_context_destroy(context);
362
363 if ((p = lws_cmdline_option(argc, argv, "--expected-exit")))
364 expected = atoi(p);
365
366 if (bad == expected) {
367 lwsl_user("Completed: OK (seen expected %d)\n", expected);
368 return 0;
369 }
370
371 lwsl_err("Completed: failed: exit %d, expected %d\n", bad, expected);
372
373 return 1;
374 }
375