/* * lws-minimal-secure-streams-smd * * Written in 2010-2020 by Andy Green * * This file is made available under the Creative Commons CC0 1.0 * Universal Public Domain Dedication. * * * This demonstrates a minimal http client using secure streams to access the * SMD api. */ #include #include #include static int interrupted, bad = 1, count_p1, count_p2, count_tx, expected = 0; static unsigned int how_many_msg = 100, usec_interval = 1000; static lws_sorted_usec_list_t sul_timeout; /* * If the -proxy app is fulfilling our connection, then we don't need to have * the policy in the client. * * When we build with LWS_SS_USE_SSPC, the apis hook up to a proxy process over * a Unix Domain Socket. To test that, you need to separately run the * ./lws-minimal-secure-streams-proxy test app on the same machine. */ #if !defined(LWS_SS_USE_SSPC) static const char * const default_ss_policy = "{" "\"schema-version\":1," "\"s\": [" "{" /* * "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; char alternate; } myss_t; /* secure streams payload interface */ static lws_ss_state_return_t myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags) { /* * Call the helper to translate into a real smd message and forward to * this context / process smd participants... except us, since we * definitely already received it */ if (lws_smd_ss_rx_forward(userobj, buf, len)) lwsl_warn("%s: forward failed\n", __func__); count_p1++; return LWSSSSRET_OK; } static void sul_tx_periodic_cb(lws_sorted_usec_list_t *sul) { myss_t *m = lws_container_of(sul, myss_t, sul); lwsl_info("%s: requesting TX\n", __func__); if (lws_ss_request_tx(m->ss)) lwsl_info("%s: req failed\n", __func__); } static lws_ss_state_return_t myss_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, int *flags) { myss_t *m = (myss_t *)userobj; lwsl_info("%s: sending SS smd\n", __func__); /* * The SS RX isn't going to see INTERACTION messages, because its class * filter doesn't accept INTERACTION class messages. The direct * participant we also set up for the test will see them though. * * Let's alternate between sending NETWORK class smd messages and * INTERACTION so we can test both rx paths */ m->alternate++; if (m->alternate == 4) { /* * after a few, let's request a CPD check */ if (lws_smd_ss_msg_printf(lws_ss_tag(m->ss), buf, len, LWSSMDCL_NETWORK, "{\"trigger\": \"cpdcheck\", " "\"src\":\"SS-test\"}")) return LWSSSSRET_TX_DONT_SEND; } else if (lws_smd_ss_msg_printf(lws_ss_tag(m->ss), buf, len, (m->alternate & 1) ? LWSSMDCL_NETWORK : LWSSMDCL_INTERACTION, (m->alternate & 1) ? "{\"class\":\"NETWORK\",\"x\":%d}" : "{\"class\":\"INTERACTION\",\"x\":%d}", count_tx)) return LWSSSSRET_TX_DONT_SEND; *flags = LWSSS_FLAG_SOM | LWSSS_FLAG_EOM; count_tx++; lws_sul_schedule(lws_ss_get_context(m->ss), 0, &m->sul, sul_tx_periodic_cb, usec_interval); return LWSSSSRET_OK; } static lws_ss_state_return_t myss_state(void *userobj, void *h_src, lws_ss_constate_t state, lws_ss_tx_ordinal_t ack) { myss_t *m = (myss_t *)userobj; lwsl_notice("%s: %s: %s (%d), ord 0x%x\n", __func__, lws_ss_tag(m->ss), lws_ss_state_name((int)state), state, (unsigned int)ack); if (state == LWSSSCS_DESTROYING) { lws_sul_cancel(&m->sul); return LWSSSSRET_OK; } if (state == LWSSSCS_CONNECTED) { lwsl_notice("%s: CONNECTED\n", __func__); lws_sul_schedule(lws_ss_get_context(m->ss), 0, &m->sul, sul_tx_periodic_cb, 1); return LWSSSSRET_OK; } return LWSSSSRET_OK; } static const lws_ss_info_t ssi_lws_smd = { .handle_offset = offsetof(myss_t, ss), .opaque_user_data_offset = offsetof(myss_t, opaque_data), .rx = myss_rx, .tx = myss_tx, .state = myss_state, .user_alloc = sizeof(myss_t), .streamtype = LWS_SMD_STREAMTYPENAME, .manual_initial_tx_credit = LWSSMDCL_SYSTEM_STATE | LWSSMDCL_METRICS | LWSSMDCL_NETWORK, }; /* for comparison, this is a non-SS lws_smd participant */ static int direct_smd_cb(void *opaque, lws_smd_class_t _class, lws_usec_t timestamp, void *buf, size_t len) { struct lws_context **pctx = (struct lws_context **)opaque; // lwsl_notice("%s: class: 0x%x, ts: %llu\n", __func__, _class, // (unsigned long long)timestamp); // lwsl_hexdump_notice(buf, len); count_p2++; if (_class != LWSSMDCL_SYSTEM_STATE) return 0; if (!lws_json_simple_strcmp(buf, len, "\"state\":", "OPERATIONAL")) { #if !defined(LWS_SS_USE_SSPC) /* * Let's trigger a CPD check, just as a test. SS can't see it * anyway since it doesn't listen for NETWORK but the direct / * local participant will see it and the result * * This process doesn't run the smd / captive portal action * when it's a client of the SS proxy. SMD has to be passed * via the SS _lws_smd proxied connection in that case. */ (void)lws_smd_msg_printf(*pctx, LWSSMDCL_NETWORK, "{\"trigger\": \"cpdcheck\", \"src\":\"direct-test\"}"); #endif /* * Create the SS link to lws_smd... notice in ssi_lws_smd * above, we tell this link to use a class filter that excludes * NETWORK messages. */ if (lws_ss_create(*pctx, 0, &ssi_lws_smd, NULL, NULL, NULL, NULL)) { lwsl_err("%s: failed to create secure stream\n", __func__); interrupted = 1; lws_cancel_service(*pctx); return -1; } } return 0; } static void sul_timeout_cb(lws_sorted_usec_list_t *sul) { lwsl_notice("%s: test finishing\n", __func__); interrupted = 1; } static void sigint_handler(int sig) { interrupted = 1; } extern int smd_ss_multi_test(int argc, const char **argv); int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_context *context; const char *p; signal(SIGINT, sigint_handler); memset(&info, 0, sizeof info); #if defined(LWS_SS_USE_SSPC) if (lws_cmdline_option(argc, argv, "--multi")) return smd_ss_multi_test(argc, argv); #endif lws_cmdline_option_handle_builtin(argc, argv, &info); if ((p = lws_cmdline_option(argc, argv, "--count"))) how_many_msg = (unsigned int)atol(p); if ((p = lws_cmdline_option(argc, argv, "--interval"))) usec_interval = (unsigned int)atol(p); lwsl_user("LWS Secure Streams SMD test client [-d]: " "%u msgs at %uus interval\n", how_many_msg, usec_interval); info.fd_limit_per_thread = 1 + 6 + 1; info.port = CONTEXT_PORT_NO_LISTEN; #if !defined(LWS_SS_USE_SSPC) info.pss_policies_json = default_ss_policy; #else 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; } #endif info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.early_smd_cb = direct_smd_cb; info.early_smd_class_filter = 0xffffffff; info.early_smd_opaque = &context; /* create the context */ context = lws_create_context(&info); if (!context) { lwsl_err("lws init failed\n"); return 1; } #if defined(LWS_SS_USE_SSPC) if (!lws_create_vhost(context, &info)) { lwsl_err("%s: failed to create default vhost\n", __func__); goto bail; } #endif /* set up the test timeout */ lws_sul_schedule(context, 0, &sul_timeout, sul_timeout_cb, (how_many_msg * (usec_interval + 50000)) + LWS_US_PER_SEC); /* the event loop */ while (lws_service(context, 0) >= 0 && !interrupted) ; /* compare what happened with what we expect */ #if defined(LWS_SS_USE_SSPC) /* if SSPC * * - the SS _lws_smd link does not enable INTERACTION class, so doesn't * see these messages (count_p1 is half count_tx) * * - the direct smd participant sees local state, but it doesn't send * any local CPD request, since as a client it doesn't do CPD * directly (count_p2 -= 1 compared to non-SSPC) * * - one CPD trigger is sent on the proxied SS link (countp1 += 1) */ if (count_p1 >= 6 && count_p2 >= 11 && count_tx >= 12) #else /* if not SSPC, then we can see direct smd activity */ if (count_p1 >= 2 && count_p2 >= 15 && count_tx >= 5) #endif bad = 0; lwsl_notice("%d %d %d\n", count_p1, count_p2, count_tx); #if defined(LWS_SS_USE_SSPC) bail: #endif lws_context_destroy(context); if ((p = lws_cmdline_option(argc, argv, "--expected-exit"))) expected = atoi(p); if (bad == expected) { lwsl_user("Completed: OK (seen expected %d)\n", expected); return 0; } lwsl_err("Completed: failed: exit %d, expected %d\n", bad, expected); return 1; }