mirror of
https://libwebsockets.org/repo/libwebsockets
synced 2024-12-25 23:00:12 +00:00
375 lines
9.6 KiB
C
375 lines
9.6 KiB
C
/*
|
|
* lws-minimal-secure-streams-smd
|
|
*
|
|
* Written in 2010-2020 by Andy Green <andy@warmcat.com>
|
|
*
|
|
* 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 <libwebsockets.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
|
|
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<verb>]: "
|
|
"%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;
|
|
}
|