2 * libwebsockets-test-client - libwebsockets test implementation
4 * Written in 2010-2019 by Andy Green <andy@warmcat.com>
6 * This file is made available under the Creative Commons CC0 1.0
7 * Universal Public Domain Dedication.
9 * The person who associated a work with this deed has dedicated
10 * the work to the public domain by waiving all of his or her rights
11 * to the work worldwide under copyright law, including all related
12 * and neighboring rights, to the extent allowed by law. You can copy,
13 * modify, distribute and perform the work, even for commercial purposes,
14 * all without asking permission.
16 * The test apps are intended to be adapted for use in your code, which
17 * may be proprietary. So unlike the library itself, they are licensed
21 #include "lws_config.h"
25 #if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32)
33 #include "gettimeofday.h"
40 #include <libwebsockets.h>
46 #define block_size (3 * 4096)
48 static int deny_deflate, longlived, mirror_lifetime, test_post, once;
49 static struct lws *wsi_dumb, *wsi_mirror;
50 static struct lws *wsi_multi[3];
51 static volatile int force_exit;
52 static unsigned int opts, rl_multi[3];
53 static int flag_no_mirror_traffic, justmirror, flag_echo;
54 static uint32_t count_blocks = 1024, txb, rxb, rx_count, errs;
55 static struct lws_poly_gen tx = { { 0xabcde, 0x23456789 } },
56 rx = { { 0xabcde, 0x23456789 } }
59 #if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param)
60 char crl_path[1024] = "";
64 * This demo shows how to connect multiple websockets simultaneously to a
65 * websocket server (there is no restriction on their having to be the same
66 * server just it simplifies the demo).
68 * dumb-increment-protocol: we connect to the server and print the number
71 * lws-mirror-protocol: draws random circles, which are mirrored on to every
72 * client (see them being drawn in every browser
73 * session also using the test server)
78 PROTOCOL_DUMB_INCREMENT,
86 lws_poly_rand(struct lws_poly_gen *p)
88 p->cyc[0] = (p->cyc[0] & 1) ? (p->cyc[0] >> 1) ^ 0xb4bcd35c :
90 p->cyc[0] = (p->cyc[0] & 1) ? (p->cyc[0] >> 1) ^ 0xb4bcd35c :
92 p->cyc[1] = (p->cyc[1] & 1) ? (p->cyc[1] >> 1) ^ 0x7a5bc2e3 :
95 return p->cyc[0] ^ p->cyc[1];
98 static void show_http_content(const char *p, size_t l)
100 if (lwsl_visible(LLL_INFO)) {
111 * dumb_increment protocol
113 * since this also happens to be protocols[0], some callbacks that are not
114 * bound to a specific protocol also turn up here.
118 callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
119 void *user, void *in, size_t len)
121 #if defined(LWS_WITH_TLS)
122 union lws_tls_cert_info_results ci;
124 const char *which = "http";
125 char which_wsi[10], buf[50 + LWS_PRE];
130 case LWS_CALLBACK_CLIENT_ESTABLISHED:
131 lwsl_info("dumb: LWS_CALLBACK_CLIENT_ESTABLISHED\n");
134 case LWS_CALLBACK_CLOSED:
135 lwsl_notice("dumb: LWS_CALLBACK_CLOSED\n");
139 case LWS_CALLBACK_CLIENT_RECEIVE:
140 ((char *)in)[len] = '\0';
141 lwsl_info("rx %d '%s'\n", (int)len, (char *)in);
144 /* because we are protocols[0] ... */
146 case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
147 if (wsi == wsi_dumb) {
151 if (wsi == wsi_mirror) {
156 for (n = 0; n < (int)LWS_ARRAY_SIZE(wsi_multi); n++)
157 if (wsi == wsi_multi[n]) {
158 sprintf(which_wsi, "multi %d", n);
163 lwsl_err("CLIENT_CONNECTION_ERROR: %s: %s\n", which,
164 in ? (char *)in : "(null)");
167 case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED:
168 if ((strcmp((const char *)in, "deflate-stream") == 0) &&
170 lwsl_notice("denied deflate-stream extension\n");
173 if ((strcmp((const char *)in, "x-webkit-deflate-frame") == 0))
175 if ((strcmp((const char *)in, "deflate-frame") == 0))
179 case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
180 lwsl_notice("lws_http_client_http_response %d\n",
181 lws_http_client_http_response(wsi));
182 #if defined(LWS_WITH_TLS)
183 if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_COMMON_NAME,
184 &ci, sizeof(ci.ns.name)))
185 lwsl_notice(" Peer Cert CN : %s\n", ci.ns.name);
187 if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_ISSUER_NAME,
188 &ci, sizeof(ci.ns.name)))
189 lwsl_notice(" Peer Cert issuer : %s\n", ci.ns.name);
191 if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_VALIDITY_FROM,
193 lwsl_notice(" Peer Cert Valid from: %s", ctime(&ci.time));
195 if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_VALIDITY_TO,
197 lwsl_notice(" Peer Cert Valid to : %s", ctime(&ci.time));
198 if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_USAGE,
200 lwsl_notice(" Peer Cert usage bits: 0x%x\n", ci.usage);
204 /* chunked content */
205 case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
206 lwsl_notice("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: %ld\n",
208 show_http_content(in, len);
211 /* unchunked content */
212 case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
214 char buffer[1024 + LWS_PRE];
215 char *px = buffer + LWS_PRE;
216 int lenx = sizeof(buffer) - LWS_PRE;
219 * Often you need to flow control this by something
220 * else being writable. In that case call the api
221 * to get a callback when writable here, and do the
222 * pending client read in the writeable callback of
225 * In the case of chunked content, this will call back
226 * LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ once per
227 * chunk or partial chunk in the buffer, and report
228 * zero length back here.
230 if (lws_http_client_read(wsi, &px, &lenx) < 0)
235 case LWS_CALLBACK_CLIENT_WRITEABLE:
236 lwsl_info("Client wsi %p writable\n", wsi);
239 case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
241 unsigned char **p = (unsigned char **)in, *end = (*p) + len;
243 if (lws_add_http_header_by_token(wsi,
244 WSI_TOKEN_HTTP_CONTENT_LENGTH,
245 (unsigned char *)"29", 2, p, end))
247 if (lws_add_http_header_by_token(wsi,
248 WSI_TOKEN_HTTP_CONTENT_TYPE,
249 (unsigned char *)"application/x-www-form-urlencoded",
253 /* inform lws we have http body to send */
254 lws_client_http_body_pending(wsi, 1);
255 lws_callback_on_writable(wsi);
259 case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE:
260 strcpy(buf + LWS_PRE, "text=hello&send=Send+the+form");
261 n = lws_write(wsi, (unsigned char *)&buf[LWS_PRE],
262 strlen(&buf[LWS_PRE]), LWS_WRITE_HTTP);
265 /* we only had one thing to send, so inform lws we are done
266 * if we had more to send, call lws_callback_on_writable(wsi);
267 * and just return 0 from callback. On having sent the last
268 * part, call the below api instead.*/
269 lws_client_http_body_pending(wsi, 0);
272 case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
277 #if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param) && \
278 !defined(LWS_WITH_MBEDTLS)
279 case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS:
281 /* Enable CRL checking of the server certificate */
282 X509_VERIFY_PARAM *param = X509_VERIFY_PARAM_new();
283 X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK);
284 SSL_CTX_set1_param((SSL_CTX*)user, param);
285 X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX*)user);
286 X509_LOOKUP *lookup = X509_STORE_add_lookup(store,
288 int n = X509_load_cert_crl_file(lookup, crl_path,
290 X509_VERIFY_PARAM_free(param);
294 lwsl_err("EXTRA_CLIENT_VERIFY_CERTS: "
295 "SSL error: %s (%d)\n",
296 ERR_error_string(n, errbuf), n);
311 /* lws-mirror_protocol */
315 callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason,
316 void *user, void *in, size_t len)
318 unsigned char buf[LWS_PRE + block_size], *p;
319 unsigned int rands[4];
324 case LWS_CALLBACK_CLIENT_ESTABLISHED:
326 lwsl_notice("mirror: LWS_CALLBACK_CLIENT_ESTABLISHED\n");
330 rx.cyc[0] = tx.cyc[0] = 0xabcde;
331 rx.cyc[1] = tx.cyc[1] = 0x23456789;
333 lws_callback_on_writable(wsi);
338 lws_get_random(lws_get_context(wsi), rands, sizeof(rands[0]));
339 mirror_lifetime = 16384 + (rands[0] & 65535);
340 /* useful to test single connection stability */
342 mirror_lifetime += 500000;
344 lwsl_notice("opened mirror connection with "
345 "%d lifetime\n", mirror_lifetime);
348 * mirror_lifetime is decremented each send, when it reaches
349 * zero the connection is closed in the send callback.
350 * When the close callback comes, wsi_mirror is set to NULL
351 * so a new connection will be opened
353 * start the ball rolling,
354 * LWS_CALLBACK_CLIENT_WRITEABLE will come next service
356 if (!flag_no_mirror_traffic)
357 lws_callback_on_writable(wsi);
360 case LWS_CALLBACK_CLIENT_CLOSED:
361 lwsl_notice("mirror: LWS_CALLBACK_CLOSED mirror_lifetime=%d, "
362 "rxb %d, rx_count %d\n", mirror_lifetime, rxb,
365 if (flag_echo || once)
369 case LWS_CALLBACK_CLIENT_WRITEABLE:
370 lwsl_user("LWS_CALLBACK_CLIENT_WRITEABLE\n");
371 if (flag_no_mirror_traffic)
375 for (n = 0; n < (int)block_size; n++)
376 buf[LWS_PRE + n] = lws_poly_rand(&tx);
378 n = lws_write(wsi, &buf[LWS_PRE], block_size,
379 opts | LWS_WRITE_TEXT);
381 lwsl_err("Error sending\n");
386 if (txb != count_blocks)
387 lws_callback_on_writable(wsi);
389 lwsl_notice("send completed: %d x %d\n",
390 count_blocks, block_size);
395 for (n = 0; n < 1; n++) {
396 lws_get_random(lws_get_context(wsi), rands,
398 l += sprintf((char *)&buf[LWS_PRE + l],
400 rands[0] & 0xffffff, /* colour */
401 rands[1] & 511, /* x */
402 rands[2] & 255, /* y */
403 (rands[3] & 31) + 1); /* radius */
406 n = lws_write(wsi, &buf[LWS_PRE], l,
407 opts | LWS_WRITE_TEXT);
411 lwsl_err("Partial write LWS_CALLBACK_CLIENT_WRITEABLE\n");
416 if (!mirror_lifetime) {
417 lwsl_notice("closing mirror session\n");
420 /* get notified as soon as we can write again */
421 lws_callback_on_writable(wsi);
423 #if !defined(_WIN32) && !defined(WIN32)
428 case LWS_CALLBACK_CLIENT_RECEIVE:
430 p = (unsigned char *)in;
431 for (n = 0; n < (int)len; n++)
432 if (*p++ != lws_poly_rand(&rx)) {
433 lwsl_err("mismatch at rxb %d offset %d\n", rxb + (n / block_size), n % block_size);
438 rx_count += (unsigned int)(unsigned long long)len;
439 while (rx_count >= block_size) {
440 rx_count -= block_size;
443 if (rx_count == 0 && rxb == count_blocks) {
444 lwsl_notice("Everything received: errs %d\n",
459 callback_test_raw_client(struct lws *wsi, enum lws_callback_reasons reason,
460 void *user, void *in, size_t len)
463 case LWS_CALLBACK_RAW_ADOPT:
464 lwsl_notice("LWS_CALLBACK_RAW_ADOPT\n");
467 case LWS_CALLBACK_RAW_RX:
468 lwsl_notice("LWS_CALLBACK_RAW_RX %ld\n", (long)len);
472 case LWS_CALLBACK_RAW_CLOSE:
473 lwsl_notice("LWS_CALLBACK_RAW_CLOSE\n");
476 case LWS_CALLBACK_RAW_WRITEABLE:
477 lwsl_notice("LWS_CALLBACK_RAW_WRITEABLE\n");
487 /* list of supported protocols and callbacks */
489 static const struct lws_protocols protocols[] = {
491 "dumb-increment-protocol",
492 callback_dumb_increment,
497 "lws-mirror-protocol",
502 "lws-test-raw-client",
503 callback_test_raw_client,
507 { NULL, NULL, 0, 0 } /* end */
510 static const struct lws_extension exts[] = {
512 "permessage-deflate",
513 lws_extension_callback_pm_deflate,
514 "permessage-deflate; client_no_context_takeover"
518 lws_extension_callback_pm_deflate,
521 { NULL, NULL, NULL /* terminator */ }
526 void sighandler(int sig)
531 #if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32)
532 static struct option options[] = {
533 { "help", no_argument, NULL, 'h' },
534 { "debug", required_argument, NULL, 'd' },
535 { "port", required_argument, NULL, 'p' },
536 { "ssl", no_argument, NULL, 's' },
537 { "strict-ssl", no_argument, NULL, 'S' },
538 { "version", required_argument, NULL, 'v' },
539 { "undeflated", no_argument, NULL, 'u' },
540 { "echo", no_argument, NULL, 'e' },
541 { "multi-test", no_argument, NULL, 'm' },
542 { "nomirror", no_argument, NULL, 'n' },
543 { "justmirror", no_argument, NULL, 'j' },
544 { "longlived", no_argument, NULL, 'l' },
545 { "post", no_argument, NULL, 'o' },
546 { "once", no_argument, NULL, 'O' },
547 { "pingpong-secs", required_argument, NULL, 'P' },
548 { "ssl-cert", required_argument, NULL, 'C' },
549 { "ssl-key", required_argument, NULL, 'K' },
550 { "ssl-ca", required_argument, NULL, 'A' },
551 #if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param)
552 { "ssl-crl", required_argument, NULL, 'R' },
558 static int ratelimit_connects(unsigned int *last, unsigned int secs)
562 gettimeofday(&tv, NULL);
564 if (tv.tv_sec - (*last) < secs)
572 int main(int argc, char **argv)
574 int n = 0, m, ret = 0, port = 7681, use_ssl = 0, ietf_version = -1;
575 unsigned int rl_dumb = 0, rl_mirror = 0, do_ws = 1, pp_secs = 0,
577 struct lws_context_creation_info info;
578 struct lws_client_connect_info i;
579 struct lws_context *context;
580 const char *prot, *p;
582 char cert_path[1024] = "";
583 char key_path[1024] = "";
584 char ca_path[1024] = "";
585 unsigned long last = lws_now_secs();
587 memset(&info, 0, sizeof info);
589 lwsl_notice("libwebsockets test client - license LGPL2.1+SLE\n");
590 lwsl_notice("(C) Copyright 2010-2018 Andy Green <andy@warmcat.com>\n");
596 #if defined(LWS_HAS_GETOPT_LONG) || defined(WIN32)
597 n = getopt_long(argc, argv, "Sjnuv:hsp:d:lC:K:A:P:moeO", options, NULL);
599 n = getopt(argc, argv, "Sjnuv:hsp:d:lC:K:A:P:moeO");
605 lws_set_log_level(atoi(optarg), NULL);
607 case 's': /* lax SSL, allow selfsigned, skip checking hostname */
608 use_ssl = LCCSCF_USE_SSL |
609 LCCSCF_ALLOW_SELFSIGNED |
610 LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK;
612 case 'S': /* Strict SSL, no selfsigned, check server hostname */
613 use_ssl = LCCSCF_USE_SSL;
622 pp_secs = atoi(optarg);
623 lwsl_notice("Setting pingpong interval to %d\n", pp_secs);
632 ietf_version = atoi(optarg);
647 flag_no_mirror_traffic = 1;
648 lwsl_notice("Disabled sending mirror data (for pingpong testing)\n");
651 lws_strncpy(cert_path, optarg, sizeof(cert_path));
654 lws_strncpy(key_path, optarg, sizeof(key_path));
657 lws_strncpy(ca_path, optarg, sizeof(ca_path));
660 #if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param)
662 lws_strncpy(crl_path, optarg, sizeof(crl_path));
673 signal(SIGINT, sighandler);
675 memset(&i, 0, sizeof(i));
678 if (lws_parse_uri(argv[optind], &prot, &i.address, &i.port, &p))
681 /* add back the leading / on path */
684 lws_strncpy(path + 1, p, sizeof(path) - 1);
689 if (!strcmp(prot, "http") || !strcmp(prot, "ws"))
691 if (!strcmp(prot, "https") || !strcmp(prot, "wss"))
693 use_ssl = LCCSCF_USE_SSL;
695 lwsl_debug("'%s' %p '%s' %p\n", i.address, i.address, i.path, i.path);
698 * create the websockets context. This tracks open connections and
699 * knows how to route any traffic and which protocol version to use,
700 * and if each connection is client or server side.
702 * For this client-only demo, we tell it to not listen on any port.
705 info.port = CONTEXT_PORT_NO_LISTEN;
706 info.protocols = protocols;
709 info.ws_ping_pong_interval = pp_secs;
710 info.extensions = exts;
713 * since we know this lws context is only ever going to be used with
714 * a few client wsis / fds / sockets at a time, let lws know it doesn't
715 * have to use the default allocations for fd tables up to ulimit -n.
716 * It will just allocate for 2 internal and 4 that we might use.
718 info.fd_limit_per_thread = 2 + 4;
720 #if defined(LWS_WITH_TLS)
721 info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
724 info.options |= LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW;
728 * If the server wants us to present a valid SSL client certificate
729 * then we can set it up here.
733 info.client_ssl_cert_filepath = cert_path;
735 info.client_ssl_private_key_filepath = key_path;
738 * A CA cert and CRL can be used to validate the cert send by the server
741 info.client_ssl_ca_filepath = ca_path;
743 #if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param)
744 else if (crl_path[0])
745 lwsl_notice("WARNING, providing a CRL requires a CA cert!\n");
749 if (use_ssl & LCCSCF_USE_SSL) {
750 lwsl_notice(" Using SSL\n");
751 #if defined(LWS_WITH_MBEDTLS)
752 lwsl_notice(" (NOTE: mbedtls needs to be given the remote\n");
753 lwsl_notice(" CA cert to trust (with -A) to validate it)\n");
757 lwsl_notice(" SSL disabled\n");
758 if (use_ssl & LCCSCF_ALLOW_SELFSIGNED)
759 lwsl_notice(" Selfsigned certs allowed\n");
761 lwsl_notice(" Cert must validate correctly (use -s to allow selfsigned)\n");
762 if (use_ssl & LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK)
763 lwsl_notice(" Skipping peer cert hostname check\n");
765 lwsl_notice(" Requiring peer cert hostname matches\n");
767 context = lws_create_context(&info);
768 if (context == NULL) {
769 fprintf(stderr, "Creating libwebsocket context failed\n");
774 i.ssl_connection = use_ssl;
776 i.origin = i.address;
777 i.ietf_version_or_minus_one = ietf_version;
779 if (!strcmp(prot, "http") || !strcmp(prot, "https")) {
780 lwsl_notice("using %s mode (non-ws)\n", prot);
783 lwsl_notice("POST mode\n");
789 if (!strcmp(prot, "raw")) {
791 i.protocol = "lws-test-raw-client";
792 lwsl_notice("using RAW mode connection\n");
795 lwsl_notice("using %s mode (ws)\n", prot);
798 * sit there servicing the websocket context to handle incoming
799 * packets, and drawing random circles on the mirror protocol websocket
801 * nothing happens until the client websocket connection is
802 * asynchronously established... calling lws_client_connect() only
803 * instantiates the connection logically, lws_service() progresses it
808 while (!force_exit) {
811 for (n = 0; n < (int)LWS_ARRAY_SIZE(wsi_multi); n++) {
812 if (!wsi_multi[n] && ratelimit_connects(&rl_multi[n], 2u)) {
813 lwsl_notice("dumb %d: connecting\n", n);
814 i.protocol = protocols[PROTOCOL_DUMB_INCREMENT].name;
815 i.pwsi = &wsi_multi[n];
816 lws_client_connect_via_info(&i);
822 if (!flag_echo && !justmirror && !wsi_dumb && ratelimit_connects(&rl_dumb, 2u)) {
823 lwsl_notice("dumb: connecting\n");
824 i.protocol = protocols[PROTOCOL_DUMB_INCREMENT].name;
826 lws_client_connect_via_info(&i);
829 if (!wsi_mirror && ratelimit_connects(&rl_mirror, 2u)) {
830 lwsl_notice("mirror: connecting\n");
831 i.protocol = protocols[PROTOCOL_LWS_MIRROR].name;
832 i.pwsi = &wsi_mirror;
833 wsi_mirror = lws_client_connect_via_info(&i);
836 if (!wsi_dumb && ratelimit_connects(&rl_dumb, 2u)) {
837 lwsl_notice("http: connecting\n");
839 lws_client_connect_via_info(&i);
843 lws_service(context, 500);
849 lwsl_notice("doing lws_callback_on_writable_all_protocol\n");
850 lws_callback_on_writable_all_protocol(context,
851 &protocols[PROTOCOL_DUMB_INCREMENT]);
855 if (flag_echo && lws_now_secs() != last) {
856 lwsl_notice("rxb %d, rx_count %d\n", rxb, rx_count);
857 last = lws_now_secs();
861 lwsl_err("Exiting\n");
862 lws_context_destroy(context);
867 fprintf(stderr, "Usage: libwebsockets-test-client "
868 "<server address> [--port=<p>] "
869 "[--ssl] [-k] [-v <ver>] "
870 "[-d <log bitfield>] [-l]\n");