client: fix redirect ssl to ssl
[platform/upstream/libwebsockets.git] / test-server / test-client.c
1 /*
2  * libwebsockets-test-client - libwebsockets test implementation
3  *
4  * Copyright (C) 2011-2016 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  * 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.
15  *
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
18  * Public Domain.
19  */
20
21 #include "lws_config.h"
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <getopt.h>
26 #include <string.h>
27 #include <signal.h>
28
29 #ifdef _WIN32
30 #define random rand
31 #include "gettimeofday.h"
32 #else
33 #include <syslog.h>
34 #include <sys/time.h>
35 #include <unistd.h>
36 #endif
37
38 #include "../lib/libwebsockets.h"
39
40 static int deny_deflate, longlived, mirror_lifetime, test_post;
41 static struct lws *wsi_dumb, *wsi_mirror;
42 static struct lws *wsi_multi[3];
43 static volatile int force_exit;
44 static unsigned int opts, rl_multi[3];
45 static int flag_no_mirror_traffic;
46
47 #if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
48 char crl_path[1024] = "";
49 #endif
50
51 /*
52  * This demo shows how to connect multiple websockets simultaneously to a
53  * websocket server (there is no restriction on their having to be the same
54  * server just it simplifies the demo).
55  *
56  *  dumb-increment-protocol:  we connect to the server and print the number
57  *                              we are given
58  *
59  *  lws-mirror-protocol: draws random circles, which are mirrored on to every
60  *                              client (see them being drawn in every browser
61  *                              session also using the test server)
62  */
63
64 enum demo_protocols {
65
66         PROTOCOL_DUMB_INCREMENT,
67         PROTOCOL_LWS_MIRROR,
68
69         /* always last */
70         DEMO_PROTOCOL_COUNT
71 };
72
73 static void show_http_content(const char *p, size_t l)
74 {
75         if (lwsl_visible(LLL_INFO)) {
76                 while (l--)
77                         if (*p < 0x7f)
78                                 putchar(*p++);
79                         else
80                                 putchar('.');
81         }
82 }
83
84
85 /*
86  * dumb_increment protocol
87  *
88  * since this also happens to be protocols[0], some callbacks that are not
89  * bound to a specific protocol also turn up here.
90  */
91
92 static int
93 callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
94                         void *user, void *in, size_t len)
95 {
96         const char *which = "http";
97         char which_wsi[10], buf[50 + LWS_PRE];
98         int n;
99
100         switch (reason) {
101
102         case LWS_CALLBACK_CLIENT_ESTABLISHED:
103                 lwsl_info("dumb: LWS_CALLBACK_CLIENT_ESTABLISHED\n");
104                 break;
105
106         case LWS_CALLBACK_CLOSED:
107                 lwsl_notice("dumb: LWS_CALLBACK_CLOSED\n");
108                 wsi_dumb = NULL;
109                 break;
110
111         case LWS_CALLBACK_CLIENT_RECEIVE:
112                 ((char *)in)[len] = '\0';
113                 lwsl_info("rx %d '%s'\n", (int)len, (char *)in);
114                 break;
115
116         /* because we are protocols[0] ... */
117
118         case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
119                 if (wsi == wsi_dumb) {
120                         which = "dumb";
121                         wsi_dumb = NULL;
122                 }
123                 if (wsi == wsi_mirror) {
124                         which = "mirror";
125                         wsi_mirror = NULL;
126                 }
127
128                 for (n = 0; n < ARRAY_SIZE(wsi_multi); n++)
129                         if (wsi == wsi_multi[n]) {
130                                 sprintf(which_wsi, "multi %d", n);
131                                 which = which_wsi;
132                                 wsi_multi[n] = NULL;
133                         }
134
135                 lwsl_err("CLIENT_CONNECTION_ERROR: %s: %s\n", which,
136                          in ? (char *)in : "(null)");
137                 break;
138
139         case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED:
140                 if ((strcmp((const char *)in, "deflate-stream") == 0) && deny_deflate) {
141                         lwsl_notice("denied deflate-stream extension\n");
142                         return 1;
143                 }
144                 if ((strcmp((const char *)in, "x-webkit-deflate-frame") == 0))
145                         return 1;
146                 if ((strcmp((const char *)in, "deflate-frame") == 0))
147                         return 1;
148                 break;
149
150         case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
151                 lwsl_notice("lws_http_client_http_response %d\n",
152                                 lws_http_client_http_response(wsi));
153                 break;
154
155         /* chunked content */
156         case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
157                 lwsl_notice("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: %ld\n",
158                             (long)len);
159                 show_http_content(in, len);
160                 break;
161
162         /* unchunked content */
163         case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
164                 {
165                         char buffer[1024 + LWS_PRE];
166                         char *px = buffer + LWS_PRE;
167                         int lenx = sizeof(buffer) - LWS_PRE;
168
169                         /*
170                          * Often you need to flow control this by something
171                          * else being writable.  In that case call the api
172                          * to get a callback when writable here, and do the
173                          * pending client read in the writeable callback of
174                          * the output.
175                          *
176                          * In the case of chunked content, this will call back
177                          * LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ once per
178                          * chunk or partial chunk in the buffer, and report
179                          * zero length back here.
180                          */
181                         if (lws_http_client_read(wsi, &px, &lenx) < 0)
182                                 return -1;
183                 }
184                 break;
185
186         case LWS_CALLBACK_CLIENT_WRITEABLE:
187                 lwsl_info("Client wsi %p writable\n", wsi);
188                 break;
189
190         case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
191                 if (test_post) {
192                         unsigned char **p = (unsigned char **)in, *end = (*p) + len;
193
194                         if (lws_add_http_header_by_token(wsi,
195                                         WSI_TOKEN_HTTP_CONTENT_LENGTH,
196                                         (unsigned char *)"29", 2, p, end))
197                                 return -1;
198                         if (lws_add_http_header_by_token(wsi,
199                                         WSI_TOKEN_HTTP_CONTENT_TYPE,
200                                         (unsigned char *)"application/x-www-form-urlencoded", 33, p, end))
201                                 return -1;
202
203                         /* inform lws we have http body to send */
204                         lws_client_http_body_pending(wsi, 1);
205                         lws_callback_on_writable(wsi);
206                 }
207                 break;
208
209         case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE:
210                 strcpy(buf + LWS_PRE, "text=hello&send=Send+the+form");
211                 n = lws_write(wsi, (unsigned char *)&buf[LWS_PRE], strlen(&buf[LWS_PRE]), LWS_WRITE_HTTP);
212                 if (n < 0)
213                         return -1;
214                 /* we only had one thing to send, so inform lws we are done
215                  * if we had more to send, call lws_callback_on_writable(wsi);
216                  * and just return 0 from callback.  On having sent the last
217                  * part, call the below api instead.*/
218                 lws_client_http_body_pending(wsi, 0);
219                 break;
220
221         case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
222                 wsi_dumb = NULL;
223                 force_exit = 1;
224                 break;
225
226 #if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
227         case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS:
228                 if (crl_path[0]) {
229                         /* Enable CRL checking of the server certificate */
230                         X509_VERIFY_PARAM *param = X509_VERIFY_PARAM_new();
231                         X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK);
232                         SSL_CTX_set1_param((SSL_CTX*)user, param);
233                         X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX*)user);
234                         X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
235                         int n = X509_load_cert_crl_file(lookup, crl_path, X509_FILETYPE_PEM);
236                         X509_VERIFY_PARAM_free(param);
237                         if (n != 1) {
238                                 char errbuf[256];
239                                 n = ERR_get_error();
240                                 lwsl_err("LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS: SSL error: %s (%d)\n", ERR_error_string(n, errbuf), n);
241                                 return 1;
242                         }
243                 }
244                 break;
245 #endif
246
247         default:
248                 break;
249         }
250
251         return 0;
252 }
253
254
255 /* lws-mirror_protocol */
256
257
258 static int
259 callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason,
260                     void *user, void *in, size_t len)
261 {
262         unsigned char buf[LWS_PRE + 4096];
263         unsigned int rands[4];
264         int l = 0;
265         int n;
266
267         switch (reason) {
268         case LWS_CALLBACK_CLIENT_ESTABLISHED:
269
270                 lwsl_notice("mirror: LWS_CALLBACK_CLIENT_ESTABLISHED\n");
271
272                 lws_get_random(lws_get_context(wsi), rands, sizeof(rands[0]));
273                 mirror_lifetime = 16384 + (rands[0] & 65535);
274                 /* useful to test single connection stability */
275                 if (longlived)
276                         mirror_lifetime += 500000;
277
278                 lwsl_info("opened mirror connection with "
279                           "%d lifetime\n", mirror_lifetime);
280
281                 /*
282                  * mirror_lifetime is decremented each send, when it reaches
283                  * zero the connection is closed in the send callback.
284                  * When the close callback comes, wsi_mirror is set to NULL
285                  * so a new connection will be opened
286                  *
287                  * start the ball rolling,
288                  * LWS_CALLBACK_CLIENT_WRITEABLE will come next service
289                  */
290                 if (!flag_no_mirror_traffic)
291                         lws_callback_on_writable(wsi);
292                 break;
293
294         case LWS_CALLBACK_CLOSED:
295                 lwsl_notice("mirror: LWS_CALLBACK_CLOSED mirror_lifetime=%d\n", mirror_lifetime);
296                 wsi_mirror = NULL;
297                 break;
298
299         case LWS_CALLBACK_CLIENT_WRITEABLE:
300                 if (flag_no_mirror_traffic)
301                         return 0;
302                 for (n = 0; n < 1; n++) {
303                         lws_get_random(lws_get_context(wsi), rands, sizeof(rands));
304                         l += sprintf((char *)&buf[LWS_PRE + l],
305                                         "c #%06X %u %u %u;",
306                                         rands[0] & 0xffffff,    /* colour */
307                                         rands[1] & 511,         /* x */
308                                         rands[2] & 255,         /* y */
309                                         (rands[3] & 31) + 1);   /* radius */
310                 }
311
312                 n = lws_write(wsi, &buf[LWS_PRE], l,
313                               opts | LWS_WRITE_TEXT);
314                 if (n < 0)
315                         return -1;
316                 if (n < l) {
317                         lwsl_err("Partial write LWS_CALLBACK_CLIENT_WRITEABLE\n");
318                         return -1;
319                 }
320
321                 mirror_lifetime--;
322                 if (!mirror_lifetime) {
323                         lwsl_info("closing mirror session\n");
324                         return -1;
325                 }
326                 /* get notified as soon as we can write again */
327                 lws_callback_on_writable(wsi);
328                 break;
329
330         default:
331                 break;
332         }
333
334         return 0;
335 }
336
337 static int
338 callback_test_raw_client(struct lws *wsi, enum lws_callback_reasons reason,
339                          void *user, void *in, size_t len)
340 {
341         switch (reason) {
342         case LWS_CALLBACK_RAW_ADOPT:
343                 lwsl_notice("LWS_CALLBACK_RAW_ADOPT\n");
344                 break;
345
346         case LWS_CALLBACK_RAW_RX:
347                 lwsl_notice("LWS_CALLBACK_RAW_RX %ld\n", (long)len);
348                 puts(in);
349                 break;
350
351         case LWS_CALLBACK_RAW_CLOSE:
352                 lwsl_notice("LWS_CALLBACK_RAW_CLOSE\n");
353                 break;
354
355         case LWS_CALLBACK_RAW_WRITEABLE:
356                 lwsl_notice("LWS_CALLBACK_RAW_WRITEABLE\n");
357                 break;
358
359         default:
360                 break;
361         }
362
363         return 0;
364 }
365
366 /* list of supported protocols and callbacks */
367
368 static const struct lws_protocols protocols[] = {
369         {
370                 "dumb-increment-protocol",
371                 callback_dumb_increment,
372                 0,
373                 20,
374         },
375         {
376                 "lws-mirror-protocol",
377                 callback_lws_mirror,
378                 0,
379                 128,
380         }, {
381                 "lws-test-raw-client",
382                 callback_test_raw_client,
383                 0,
384                 128
385         },
386         { NULL, NULL, 0, 0 } /* end */
387 };
388
389 static const struct lws_extension exts[] = {
390         {
391                 "permessage-deflate",
392                 lws_extension_callback_pm_deflate,
393                 "permessage-deflate; client_no_context_takeover"
394         },
395         {
396                 "deflate-frame",
397                 lws_extension_callback_pm_deflate,
398                 "deflate_frame"
399         },
400         { NULL, NULL, NULL /* terminator */ }
401 };
402
403
404
405 void sighandler(int sig)
406 {
407         force_exit = 1;
408 }
409
410 static struct option options[] = {
411         { "help",       no_argument,            NULL, 'h' },
412         { "debug",      required_argument,      NULL, 'd' },
413         { "port",       required_argument,      NULL, 'p' },
414         { "ssl",        no_argument,            NULL, 's' },
415         { "strict-ssl", no_argument,            NULL, 'S' },
416         { "version",    required_argument,      NULL, 'v' },
417         { "undeflated", no_argument,            NULL, 'u' },
418         { "multi-test", no_argument,            NULL, 'm' },
419         { "nomirror",   no_argument,            NULL, 'n' },
420         { "longlived",  no_argument,            NULL, 'l' },
421         { "post",       no_argument,            NULL, 'o' },
422         { "pingpong-secs", required_argument,   NULL, 'P' },
423         { "ssl-cert",  required_argument,       NULL, 'C' },
424         { "ssl-key",  required_argument,        NULL, 'K' },
425         { "ssl-ca",  required_argument,         NULL, 'A' },
426 #if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
427         { "ssl-crl",  required_argument,                NULL, 'R' },
428 #endif
429         { NULL, 0, 0, 0 }
430 };
431
432 static int ratelimit_connects(unsigned int *last, unsigned int secs)
433 {
434         struct timeval tv;
435
436         gettimeofday(&tv, NULL);
437
438         if (tv.tv_sec - (*last) < secs)
439                 return 0;
440
441         *last = tv.tv_sec;
442
443         return 1;
444 }
445
446 int main(int argc, char **argv)
447 {
448         int n = 0, m, ret = 0, port = 7681, use_ssl = 0, ietf_version = -1;
449         unsigned int rl_dumb = 0, rl_mirror = 0, do_ws = 1, pp_secs = 0, do_multi = 0;
450         struct lws_context_creation_info info;
451         struct lws_client_connect_info i;
452         struct lws_context *context;
453         const char *prot, *p;
454         char path[300];
455         char cert_path[1024] = "";
456         char key_path[1024] = "";
457         char ca_path[1024] = "";
458
459         memset(&info, 0, sizeof info);
460
461         lwsl_notice("libwebsockets test client - license LGPL2.1+SLE\n");
462         lwsl_notice("(C) Copyright 2010-2016 Andy Green <andy@warmcat.com>\n");
463
464         if (argc < 2)
465                 goto usage;
466
467         while (n >= 0) {
468                 n = getopt_long(argc, argv, "Snuv:hsp:d:lC:K:A:P:mo", options, NULL);
469                 if (n < 0)
470                         continue;
471                 switch (n) {
472                 case 'd':
473                         lws_set_log_level(atoi(optarg), NULL);
474                         break;
475                 case 's': /* lax SSL, allow selfsigned, skip checking hostname */
476                         use_ssl = LCCSCF_USE_SSL |
477                                   LCCSCF_ALLOW_SELFSIGNED |
478                                   LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK;
479                         break;
480                 case 'S': /* Strict SSL, no selfsigned, check server hostname */
481                         use_ssl = LCCSCF_USE_SSL;
482                         break;
483                 case 'p':
484                         port = atoi(optarg);
485                         break;
486                 case 'P':
487                         pp_secs = atoi(optarg);
488                         lwsl_notice("Setting pingpong interval to %d\n", pp_secs);
489                         break;
490                 case 'l':
491                         longlived = 1;
492                         break;
493                 case 'v':
494                         ietf_version = atoi(optarg);
495                         break;
496                 case 'u':
497                         deny_deflate = 1;
498                         break;
499                 case 'm':
500                         do_multi = 1;
501                         break;
502                 case 'o':
503                         test_post = 1;
504                         break;
505                 case 'n':
506                         flag_no_mirror_traffic = 1;
507                         lwsl_notice("Disabled sending mirror data (for pingpong testing)\n");
508                         break;
509                 case 'C':
510                         strncpy(cert_path, optarg, sizeof(cert_path) - 1);
511                         cert_path[sizeof(cert_path) - 1] = '\0';
512                         break;
513                 case 'K':
514                         strncpy(key_path, optarg, sizeof(key_path) - 1);
515                         key_path[sizeof(key_path) - 1] = '\0';
516                         break;
517                 case 'A':
518                         strncpy(ca_path, optarg, sizeof(ca_path) - 1);
519                         ca_path[sizeof(ca_path) - 1] = '\0';
520                         break;
521
522 #if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
523                 case 'R':
524                         strncpy(crl_path, optarg, sizeof(crl_path) - 1);
525                         crl_path[sizeof(crl_path) - 1] = '\0';
526                         break;
527 #endif
528                 case 'h':
529                         goto usage;
530                 }
531         }
532
533         if (optind >= argc)
534                 goto usage;
535
536         signal(SIGINT, sighandler);
537
538         memset(&i, 0, sizeof(i));
539
540         i.port = port;
541         if (lws_parse_uri(argv[optind], &prot, &i.address, &i.port, &p))
542                 goto usage;
543
544         /* add back the leading / on path */
545         path[0] = '/';
546         strncpy(path + 1, p, sizeof(path) - 2);
547         path[sizeof(path) - 1] = '\0';
548         i.path = path;
549
550         if (!strcmp(prot, "http") || !strcmp(prot, "ws"))
551                 use_ssl = 0;
552         if (!strcmp(prot, "https") || !strcmp(prot, "wss"))
553                 if (!use_ssl)
554                         use_ssl = LCCSCF_USE_SSL;
555
556         /*
557          * create the websockets context.  This tracks open connections and
558          * knows how to route any traffic and which protocol version to use,
559          * and if each connection is client or server side.
560          *
561          * For this client-only demo, we tell it to not listen on any port.
562          */
563
564         info.port = CONTEXT_PORT_NO_LISTEN;
565         info.protocols = protocols;
566         info.gid = -1;
567         info.uid = -1;
568         info.ws_ping_pong_interval = pp_secs;
569         info.extensions = exts;
570
571 #if defined(LWS_OPENSSL_SUPPORT)
572         info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
573 #endif
574
575         if (use_ssl) {
576                 /*
577                  * If the server wants us to present a valid SSL client certificate
578                  * then we can set it up here.
579                  */
580
581                 if (cert_path[0])
582                         info.ssl_cert_filepath = cert_path;
583                 if (key_path[0])
584                         info.ssl_private_key_filepath = key_path;
585
586                 /*
587                  * A CA cert and CRL can be used to validate the cert send by the server
588                  */
589                 if (ca_path[0])
590                         info.ssl_ca_filepath = ca_path;
591
592 #if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
593                 else if (crl_path[0])
594                         lwsl_notice("WARNING, providing a CRL requires a CA cert!\n");
595 #endif
596         }
597
598         if (use_ssl & LCCSCF_USE_SSL)
599                 lwsl_notice(" Using SSL\n");
600         else
601                 lwsl_notice(" SSL disabled\n");
602         if (use_ssl & LCCSCF_ALLOW_SELFSIGNED)
603                 lwsl_notice(" Selfsigned certs allowed\n");
604         else
605                 lwsl_notice(" Cert must validate correctly (use -s to allow selfsigned)\n");
606         if (use_ssl & LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK)
607                 lwsl_notice(" Skipping peer cert hostname check\n");
608         else
609                 lwsl_notice(" Requiring peer cert hostname matches\n");
610
611         context = lws_create_context(&info);
612         if (context == NULL) {
613                 fprintf(stderr, "Creating libwebsocket context failed\n");
614                 return 1;
615         }
616
617         i.context = context;
618         i.ssl_connection = use_ssl;
619         i.host = i.address;
620         i.origin = i.address;
621         i.ietf_version_or_minus_one = ietf_version;
622
623         if (!strcmp(prot, "http") || !strcmp(prot, "https")) {
624                 lwsl_notice("using %s mode (non-ws)\n", prot);
625                 if (test_post) {
626                         i.method = "POST";
627                         lwsl_notice("POST mode\n");
628                 }
629                 else
630                         i.method = "GET";
631                 do_ws = 0;
632         } else
633                 if (!strcmp(prot, "raw")) {
634                         i.method = "RAW";
635                         i.protocol = "lws-test-raw-client";
636                         lwsl_notice("using RAW mode connection\n");
637                         do_ws = 0;
638                 } else
639                         lwsl_notice("using %s mode (ws)\n", prot);
640
641         /*
642          * sit there servicing the websocket context to handle incoming
643          * packets, and drawing random circles on the mirror protocol websocket
644          *
645          * nothing happens until the client websocket connection is
646          * asynchronously established... calling lws_client_connect() only
647          * instantiates the connection logically, lws_service() progresses it
648          * asynchronously.
649          */
650
651         m = 0;
652         while (!force_exit) {
653
654                 if (do_multi) {
655                         for (n = 0; n < ARRAY_SIZE(wsi_multi); n++) {
656                                 if (!wsi_multi[n] && ratelimit_connects(&rl_multi[n], 2u)) {
657                                         lwsl_notice("dumb %d: connecting\n", n);
658                                         i.protocol = protocols[PROTOCOL_DUMB_INCREMENT].name;
659                                         i.pwsi = &wsi_multi[n];
660                                         lws_client_connect_via_info(&i);
661                                 }
662                         }
663                 } else {
664
665                         if (do_ws) {
666                                 if (!wsi_dumb && ratelimit_connects(&rl_dumb, 2u)) {
667                                         lwsl_notice("dumb: connecting\n");
668                                         i.protocol = protocols[PROTOCOL_DUMB_INCREMENT].name;
669                                         i.pwsi = &wsi_dumb;
670                                         lws_client_connect_via_info(&i);
671                                 }
672
673                                 if (!wsi_mirror && ratelimit_connects(&rl_mirror, 2u)) {
674                                         lwsl_notice("mirror: connecting\n");
675                                         i.protocol = protocols[PROTOCOL_LWS_MIRROR].name;
676                                         i.pwsi = &wsi_mirror;
677                                         wsi_mirror = lws_client_connect_via_info(&i);
678                                 }
679                         } else
680                                 if (!wsi_dumb && ratelimit_connects(&rl_dumb, 2u)) {
681                                         lwsl_notice("http: connecting\n");
682                                         i.pwsi = &wsi_dumb;
683                                         lws_client_connect_via_info(&i);
684                                 }
685                 }
686
687                 lws_service(context, 500);
688
689                 if (do_multi) {
690                         m++;
691                         if (m == 10) {
692                                 m = 0;
693                                 lwsl_notice("doing lws_callback_on_writable_all_protocol\n");
694                                 lws_callback_on_writable_all_protocol(context, &protocols[PROTOCOL_DUMB_INCREMENT]);
695                         }
696                 }
697         }
698
699         lwsl_err("Exiting\n");
700         lws_context_destroy(context);
701
702         return ret;
703
704 usage:
705         fprintf(stderr, "Usage: libwebsockets-test-client "
706                                 "<server address> [--port=<p>] "
707                                 "[--ssl] [-k] [-v <ver>] "
708                                 "[-d <log bitfield>] [-l]\n");
709         return 1;
710 }