vcl: add QUIC support
[vpp.git] / src / plugins / hs_apps / vcl / vcl_test_client.c
1 /*
2  * Copyright (c) 2017-2019 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 #include <unistd.h>
17 #include <errno.h>
18 #include <stdlib.h>
19 #include <ctype.h>
20 #include <sys/types.h>
21 #include <sys/socket.h>
22 #include <stdio.h>
23 #include <time.h>
24 #include <arpa/inet.h>
25 #include <hs_apps/vcl/vcl_test.h>
26 #include <pthread.h>
27
28 typedef struct
29 {
30   vcl_test_session_t *sessions;
31   vcl_test_session_t *qsessions;
32   uint32_t n_sessions;
33   uint32_t n_qsessions;
34   uint32_t wrk_index;
35   fd_set wr_fdset;
36   fd_set rd_fdset;
37   int max_fd_index;
38   pthread_t thread_handle;
39   vcl_test_cfg_t cfg;
40 } vcl_test_client_worker_t;
41
42 typedef struct
43 {
44   vcl_test_client_worker_t *workers;
45   vppcom_endpt_t server_endpt;
46   uint32_t cfg_seq_num;
47   vcl_test_session_t quic_session;
48   vcl_test_session_t ctrl_session;
49   vcl_test_session_t *sessions;
50   uint8_t dump_cfg;
51   vcl_test_t post_test;
52   uint8_t proto;
53   uint32_t n_workers;
54   volatile int active_workers;
55   struct sockaddr_storage server_addr;
56 } vcl_test_client_main_t;
57
58 static __thread int __wrk_index = 0;
59
60 vcl_test_client_main_t vcl_client_main;
61
62 #define vtc_min(a, b) (a < b ? a : b)
63 #define vtc_max(a, b) (a > b ? a : b)
64
65 static int
66 vtc_cfg_sync (vcl_test_session_t * ts)
67 {
68   vcl_test_cfg_t *rx_cfg = (vcl_test_cfg_t *) ts->rxbuf;
69   int rx_bytes, tx_bytes;
70
71   vt_atomic_add (&ts->cfg.seq_num, 1);
72   if (ts->cfg.verbose)
73     {
74       vtinf ("(fd %d): Sending config to server.", ts->fd);
75       vcl_test_cfg_dump (&ts->cfg, 1 /* is_client */ );
76     }
77   tx_bytes = vcl_test_write (ts->fd, (uint8_t *) & ts->cfg,
78                              sizeof (ts->cfg), NULL, ts->cfg.verbose);
79   if (tx_bytes < 0)
80     {
81       vtwrn ("(fd %d): write test cfg failed (%d)!", ts->fd, tx_bytes);
82       return tx_bytes;
83     }
84
85   rx_bytes = vcl_test_read (ts->fd, (uint8_t *) ts->rxbuf,
86                             sizeof (vcl_test_cfg_t), NULL);
87   if (rx_bytes < 0)
88     return rx_bytes;
89
90   if (rx_cfg->magic != VCL_TEST_CFG_CTRL_MAGIC)
91     {
92       vtwrn ("(fd %d): Bad server reply cfg -- aborting!", ts->fd);
93       return -1;
94     }
95   if ((rx_bytes != sizeof (vcl_test_cfg_t))
96       || !vcl_test_cfg_verify (rx_cfg, &ts->cfg))
97     {
98       vtwrn ("(fd %d): Invalid config received from server!", ts->fd);
99       if (rx_bytes != sizeof (vcl_test_cfg_t))
100         {
101           vtinf ("\tRx bytes %d != cfg size %lu", rx_bytes,
102                  sizeof (vcl_test_cfg_t));
103         }
104       else
105         {
106           vcl_test_cfg_dump (rx_cfg, 1 /* is_client */ );
107           vtinf ("(fd %d): Valid config sent to server.", ts->fd);
108           vcl_test_cfg_dump (&ts->cfg, 1 /* is_client */ );
109         }
110       return -1;
111     }
112   if (ts->cfg.verbose)
113     {
114       vtinf ("(fd %d): Got config back from server.", ts->fd);
115       vcl_test_cfg_dump (rx_cfg, 1 /* is_client */ );
116     }
117
118   return 0;
119 }
120
121 static int
122 vtc_quic_connect_test_sessions (vcl_test_client_worker_t * wrk)
123 {
124   vcl_test_client_main_t *vcm = &vcl_client_main;
125   vcl_test_session_t *ts, *tq;
126   uint32_t i;
127   int rv;
128
129   if (wrk->cfg.num_test_sessions < 1 || wrk->cfg.num_test_sessions_perq < 1)
130     {
131       errno = EINVAL;
132       return -1;
133     }
134
135   if (wrk->n_sessions >= wrk->cfg.num_test_sessions)
136     goto done;
137
138   /* Connect Qsessions */
139
140   if (wrk->n_qsessions)
141     wrk->qsessions =
142       realloc (wrk->qsessions,
143                wrk->cfg.num_test_qsessions * sizeof (vcl_test_session_t));
144   else
145     wrk->qsessions =
146       calloc (wrk->cfg.num_test_qsessions, sizeof (vcl_test_session_t));
147
148   if (!wrk->qsessions)
149     {
150       vterr ("failed to alloc Qsessions", -errno);
151       return errno;
152     }
153
154
155   for (i = 0; i < wrk->cfg.num_test_qsessions; i++)
156     {
157       tq = &wrk->qsessions[i];
158       tq->fd = vppcom_session_create (vcm->proto, 1 /* is_nonblocking */ );
159       tq->session_index = i;
160       if (tq->fd < 0)
161         {
162           vterr ("vppcom_session_create()", tq->fd);
163           return tq->fd;
164         }
165
166       rv = vppcom_session_connect (tq->fd, &vcm->server_endpt);
167       if (rv < 0)
168         {
169           vterr ("vppcom_session_connect()", rv);
170           return rv;
171         }
172       vtinf ("Test Qsession %d (fd %d) connected.", i, tq->fd);
173     }
174   wrk->n_qsessions = wrk->cfg.num_test_qsessions;
175
176   /* Connect Stream sessions */
177
178   if (wrk->n_sessions)
179     wrk->sessions =
180       realloc (wrk->sessions,
181                wrk->cfg.num_test_sessions * sizeof (vcl_test_session_t));
182   else
183     wrk->sessions =
184       calloc (wrk->cfg.num_test_sessions, sizeof (vcl_test_session_t));
185
186   if (!wrk->sessions)
187     {
188       vterr ("failed to alloc sessions", -errno);
189       return errno;
190     }
191
192   for (i = 0; i < wrk->cfg.num_test_sessions; i++)
193     {
194       tq = &wrk->qsessions[i / wrk->cfg.num_test_sessions_perq];
195       ts = &wrk->sessions[i];
196       ts->fd = vppcom_session_create (vcm->proto, 1 /* is_nonblocking */ );
197       ts->session_index = i;
198       if (ts->fd < 0)
199         {
200           vterr ("vppcom_session_create()", ts->fd);
201           return ts->fd;
202         }
203
204       rv = vppcom_session_stream_connect (ts->fd, tq->fd);
205       if (rv < 0)
206         {
207           vterr ("vppcom_session_stream_connect()", rv);
208           return rv;
209         }
210
211       vtinf ("Test session %d (fd %d) connected.", i, ts->fd);
212     }
213   wrk->n_sessions = wrk->cfg.num_test_sessions;
214
215 done:
216   vtinf ("All test sessions (%d) connected!", wrk->cfg.num_test_sessions);
217   return 0;
218 }
219
220 static int
221 vtc_connect_test_sessions (vcl_test_client_worker_t * wrk)
222 {
223   vcl_test_client_main_t *vcm = &vcl_client_main;
224   vcl_test_session_t *ts;
225   uint32_t n_test_sessions;
226   int i, rv;
227
228   if (vcm->proto == VPPCOM_PROTO_QUIC)
229     return vtc_quic_connect_test_sessions (wrk);
230
231   n_test_sessions = wrk->cfg.num_test_sessions;
232   if (n_test_sessions < 1)
233     {
234       errno = EINVAL;
235       return -1;
236     }
237
238   if (wrk->n_sessions >= n_test_sessions)
239     goto done;
240
241   if (wrk->n_sessions)
242     wrk->sessions = realloc (wrk->sessions,
243                              n_test_sessions * sizeof (vcl_test_session_t));
244   else
245     wrk->sessions = calloc (n_test_sessions, sizeof (vcl_test_session_t));
246
247   if (!wrk->sessions)
248     {
249       vterr ("failed to alloc sessions", -errno);
250       return errno;
251     }
252
253   for (i = 0; i < n_test_sessions; i++)
254     {
255       ts = &wrk->sessions[i];
256       ts->fd = vppcom_session_create (vcm->proto, 1 /* is_nonblocking */ );
257       if (ts->fd < 0)
258         {
259           vterr ("vppcom_session_create()", ts->fd);
260           return ts->fd;
261         }
262
263       rv = vppcom_session_connect (ts->fd, &vcm->server_endpt);
264       if (rv < 0)
265         {
266           vterr ("vppcom_session_connect()", rv);
267           return rv;
268         }
269       vtinf ("Test session %d (fd %d) connected.", i, ts->fd);
270     }
271   wrk->n_sessions = n_test_sessions;
272
273 done:
274   vtinf ("All test sessions (%d) connected!", n_test_sessions);
275   return 0;
276 }
277
278 static int
279 vtc_worker_test_setup (vcl_test_client_worker_t * wrk)
280 {
281   vcl_test_client_main_t *vcm = &vcl_client_main;
282   vcl_test_session_t *ctrl = &vcm->ctrl_session;
283   vcl_test_cfg_t *cfg = &wrk->cfg;
284   vcl_test_session_t *ts;
285   uint32_t sidx;
286   int i, j;
287
288   FD_ZERO (&wrk->wr_fdset);
289   FD_ZERO (&wrk->rd_fdset);
290
291   for (i = 0; i < cfg->num_test_sessions; i++)
292     {
293       ts = &wrk->sessions[i];
294       ts->cfg = wrk->cfg;
295       vcl_test_session_buf_alloc (ts);
296
297       switch (cfg->test)
298         {
299         case VCL_TEST_TYPE_ECHO:
300           memcpy (ts->txbuf, ctrl->txbuf, cfg->total_bytes);
301           break;
302         case VCL_TEST_TYPE_UNI:
303         case VCL_TEST_TYPE_BI:
304           for (j = 0; j < ts->txbuf_size; j++)
305             ts->txbuf[j] = j & 0xff;
306           break;
307         }
308
309       FD_SET (vppcom_session_index (ts->fd), &wrk->wr_fdset);
310       FD_SET (vppcom_session_index (ts->fd), &wrk->rd_fdset);
311       sidx = vppcom_session_index (ts->fd);
312       wrk->max_fd_index = vtc_max (sidx, wrk->max_fd_index);
313     }
314   wrk->max_fd_index += 1;
315
316   return 0;
317 }
318
319 static int
320 vtc_worker_init (vcl_test_client_worker_t * wrk)
321 {
322   vcl_test_client_main_t *vcm = &vcl_client_main;
323   vcl_test_cfg_t *cfg = &wrk->cfg;
324   vcl_test_session_t *ts;
325   uint32_t n;
326   int rv;
327
328   __wrk_index = wrk->wrk_index;
329
330   vtinf ("Initializing worker %u ...", wrk->wrk_index);
331
332   if (wrk->wrk_index)
333     {
334       if (vppcom_worker_register ())
335         {
336           vtwrn ("failed to register worker");
337           return -1;
338         }
339       vt_atomic_add (&vcm->active_workers, 1);
340     }
341   rv = vtc_connect_test_sessions (wrk);
342   if (rv)
343     {
344       vterr ("vtc_connect_test_sessions ()", rv);
345       return rv;
346     }
347
348   if (vtc_worker_test_setup (wrk))
349     return -1;
350
351   vtinf ("Sending config to server on all sessions ...");
352
353   for (n = 0; n < cfg->num_test_sessions; n++)
354     {
355       ts = &wrk->sessions[n];
356       if (vtc_cfg_sync (ts))
357         return -1;
358       memset (&ts->stats, 0, sizeof (ts->stats));
359     }
360
361   return 0;
362 }
363
364 static int stats_lock = 0;
365
366 static void
367 vtc_accumulate_stats (vcl_test_client_worker_t * wrk,
368                       vcl_test_session_t * ctrl)
369 {
370   vcl_test_session_t *ts;
371   static char buf[64];
372   int i, show_rx = 0;
373
374   while (__sync_lock_test_and_set (&stats_lock, 1))
375     ;
376
377   if (ctrl->cfg.test == VCL_TEST_TYPE_BI
378       || ctrl->cfg.test == VCL_TEST_TYPE_ECHO)
379     show_rx = 1;
380
381   for (i = 0; i < wrk->cfg.num_test_sessions; i++)
382     {
383       ts = &wrk->sessions[i];
384       ts->stats.start = ctrl->stats.start;
385
386       if (ctrl->cfg.verbose > 1)
387         {
388           sprintf (buf, "CLIENT (fd %d) RESULTS", ts->fd);
389           vcl_test_stats_dump (buf, &ts->stats, show_rx, 1 /* show tx */ ,
390                                ctrl->cfg.verbose);
391         }
392
393       vcl_test_stats_accumulate (&ctrl->stats, &ts->stats);
394       if (vcl_comp_tspec (&ctrl->stats.stop, &ts->stats.stop) < 0)
395         ctrl->stats.stop = ts->stats.stop;
396     }
397
398   __sync_lock_release (&stats_lock);
399 }
400
401 static void
402 vtc_worker_sessions_exit (vcl_test_client_worker_t * wrk)
403 {
404   vcl_test_client_main_t *vcm = &vcl_client_main;
405   vcl_test_session_t *ctrl = &vcm->ctrl_session;
406   vcl_test_session_t *ts;
407   int i, verbose = ctrl->cfg.verbose;
408
409   for (i = 0; i < wrk->cfg.num_test_sessions; i++)
410     {
411       ts = &wrk->sessions[i];
412       ts->cfg.test = VCL_TEST_TYPE_EXIT;
413
414       if (verbose)
415         {
416           vtinf ("(fd %d): Sending exit cfg to server...", ts->fd);
417           vcl_test_cfg_dump (&ts->cfg, 1 /* is_client */ );
418         }
419       (void) vcl_test_write (ts->fd, (uint8_t *) & ts->cfg,
420                              sizeof (ts->cfg), &ts->stats, verbose);
421     }
422
423   wrk->n_sessions = 0;
424 }
425
426 static void *
427 vtc_worker_loop (void *arg)
428 {
429   vcl_test_client_main_t *vcm = &vcl_client_main;
430   vcl_test_session_t *ctrl = &vcm->ctrl_session;
431   vcl_test_client_worker_t *wrk = arg;
432   uint32_t n_active_sessions, n_bytes;
433   fd_set _wfdset, *wfdset = &_wfdset;
434   fd_set _rfdset, *rfdset = &_rfdset;
435   vcl_test_session_t *ts;
436   int i, rv, check_rx = 0;
437
438   rv = vtc_worker_init (wrk);
439   if (rv)
440     {
441       vterr ("vtc_worker_init()", rv);
442       return 0;
443     }
444
445   vtinf ("Starting test ...");
446
447   if (wrk->wrk_index == 0)
448     clock_gettime (CLOCK_REALTIME, &ctrl->stats.start);
449
450   check_rx = wrk->cfg.test != VCL_TEST_TYPE_UNI;
451   n_active_sessions = wrk->cfg.num_test_sessions;
452   while (n_active_sessions)
453     {
454       _wfdset = wrk->wr_fdset;
455       _rfdset = wrk->rd_fdset;
456
457       rv = vppcom_select (wrk->max_fd_index, (unsigned long *) rfdset,
458                           (unsigned long *) wfdset, NULL, 0);
459       if (rv < 0)
460         {
461           vterr ("vppcom_select()", rv);
462           goto exit;
463         }
464       else if (rv == 0)
465         continue;
466
467       for (i = 0; i < wrk->cfg.num_test_sessions; i++)
468         {
469           ts = &wrk->sessions[i];
470           if (!((ts->stats.stop.tv_sec == 0) &&
471                 (ts->stats.stop.tv_nsec == 0)))
472             continue;
473
474           if (FD_ISSET (vppcom_session_index (ts->fd), rfdset)
475               && ts->stats.rx_bytes < ts->cfg.total_bytes)
476             {
477               (void) vcl_test_read (ts->fd, (uint8_t *) ts->rxbuf,
478                                     ts->rxbuf_size, &ts->stats);
479             }
480
481           if (FD_ISSET (vppcom_session_index (ts->fd), wfdset)
482               && ts->stats.tx_bytes < ts->cfg.total_bytes)
483             {
484               n_bytes = ts->cfg.txbuf_size;
485               if (ts->cfg.test == VCL_TEST_TYPE_ECHO)
486                 n_bytes = strlen (ctrl->txbuf) + 1;
487               rv = vcl_test_write (ts->fd, (uint8_t *) ts->txbuf,
488                                    n_bytes, &ts->stats, ts->cfg.verbose);
489               if (rv < 0)
490                 {
491                   vtwrn ("vppcom_test_write (%d) failed -- aborting test",
492                          ts->fd);
493                   goto exit;
494                 }
495             }
496           if ((!check_rx && ts->stats.tx_bytes >= ts->cfg.total_bytes)
497               || (check_rx && ts->stats.rx_bytes >= ts->cfg.total_bytes))
498             {
499               clock_gettime (CLOCK_REALTIME, &ts->stats.stop);
500               n_active_sessions--;
501             }
502         }
503     }
504 exit:
505   vtinf ("Worker %d done ...", wrk->wrk_index);
506   if (wrk->cfg.test != VCL_TEST_TYPE_ECHO)
507     vtc_accumulate_stats (wrk, ctrl);
508   sleep (VCL_TEST_DELAY_DISCONNECT);
509   vtc_worker_sessions_exit (wrk);
510   if (wrk->wrk_index)
511     vt_atomic_add (&vcm->active_workers, -1);
512   return 0;
513 }
514
515 static void
516 vtc_print_stats (vcl_test_session_t * ctrl)
517 {
518   int is_echo = ctrl->cfg.test == VCL_TEST_TYPE_ECHO;
519   int show_rx = 0;
520   char buf[64];
521
522   if (ctrl->cfg.test == VCL_TEST_TYPE_BI
523       || ctrl->cfg.test == VCL_TEST_TYPE_ECHO)
524     show_rx = 1;
525
526   vcl_test_stats_dump ("CLIENT RESULTS", &ctrl->stats,
527                        show_rx, 1 /* show tx */ ,
528                        ctrl->cfg.verbose);
529   vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
530
531   if (ctrl->cfg.verbose)
532     {
533       vtinf ("  ctrl session info\n"
534              VCL_TEST_SEPARATOR_STRING
535              "          fd:  %d (0x%08x)\n"
536              "       rxbuf:  %p\n"
537              "  rxbuf size:  %u (0x%08x)\n"
538              "       txbuf:  %p\n"
539              "  txbuf size:  %u (0x%08x)\n"
540              VCL_TEST_SEPARATOR_STRING,
541              ctrl->fd, (uint32_t) ctrl->fd,
542              ctrl->rxbuf, ctrl->rxbuf_size, ctrl->rxbuf_size,
543              ctrl->txbuf, ctrl->txbuf_size, ctrl->txbuf_size);
544     }
545
546   if (is_echo)
547     sprintf (buf, "Echo");
548   else
549     sprintf (buf, "%s-directional Stream",
550              ctrl->cfg.test == VCL_TEST_TYPE_BI ? "Bi" : "Uni");
551 }
552
553 static void
554 vtc_echo_client (vcl_test_client_main_t * vcm)
555 {
556   vcl_test_client_worker_t *wrk;
557   vcl_test_session_t *ctrl = &vcm->ctrl_session;
558   vcl_test_cfg_t *cfg = &ctrl->cfg;
559
560   cfg->total_bytes = strlen (ctrl->txbuf) + 1;
561   memset (&ctrl->stats, 0, sizeof (ctrl->stats));
562
563   /* Echo works with only one worker */
564   wrk = vcm->workers;
565   wrk->wrk_index = 0;
566   wrk->cfg = *cfg;
567
568   vtc_worker_loop (wrk);
569
570   /* Not relevant for echo test
571      clock_gettime (CLOCK_REALTIME, &ctrl->stats.stop);
572      vtc_accumulate_stats (wrk, ctrl);
573      vtc_print_stats (ctrl);
574    */
575 }
576
577 static void
578 vtc_stream_client (vcl_test_client_main_t * vcm)
579 {
580   vcl_test_session_t *ctrl = &vcm->ctrl_session;
581   vcl_test_cfg_t *cfg = &ctrl->cfg;
582   vcl_test_client_worker_t *wrk;
583   uint32_t i, n_conn, n_conn_per_wrk;
584
585   vtinf ("%s-directional Stream Test Starting!",
586          ctrl->cfg.test == VCL_TEST_TYPE_BI ? "Bi" : "Uni");
587
588   cfg->total_bytes = cfg->num_writes * cfg->txbuf_size;
589   cfg->ctrl_handle = ~0;
590   if (vtc_cfg_sync (ctrl))
591     {
592       vtwrn ("test cfg sync failed -- aborting!");
593       return;
594     }
595   cfg->ctrl_handle = ((vcl_test_cfg_t *) ctrl->rxbuf)->ctrl_handle;
596   memset (&ctrl->stats, 0, sizeof (ctrl->stats));
597
598   n_conn = cfg->num_test_sessions;
599   n_conn_per_wrk = n_conn / vcm->n_workers;
600   for (i = 0; i < vcm->n_workers; i++)
601     {
602       wrk = &vcm->workers[i];
603       wrk->wrk_index = i;
604       wrk->cfg = ctrl->cfg;
605       wrk->cfg.num_test_sessions = vtc_min (n_conn_per_wrk, n_conn);
606       n_conn -= wrk->cfg.num_test_sessions;
607     }
608
609   for (i = 1; i < vcm->n_workers; i++)
610     {
611       wrk = &vcm->workers[i];
612       pthread_create (&wrk->thread_handle, NULL, vtc_worker_loop,
613                       (void *) wrk);
614     }
615   vtc_worker_loop (&vcm->workers[0]);
616
617   while (vcm->active_workers > 0)
618     ;
619
620   vtinf ("Sending config on ctrl session (fd %d) for stats...", ctrl->fd);
621   if (vtc_cfg_sync (ctrl))
622     {
623       vtwrn ("test cfg sync failed -- aborting!");
624       return;
625     }
626
627   vtc_print_stats (ctrl);
628
629   ctrl->cfg.test = VCL_TEST_TYPE_ECHO;
630   ctrl->cfg.total_bytes = 0;
631   if (vtc_cfg_sync (ctrl))
632     vtwrn ("post-test cfg sync failed!");
633 }
634
635 static void
636 dump_help (void)
637 {
638 #define INDENT "\n  "
639
640   printf ("CLIENT: Test configuration commands:"
641           INDENT VCL_TEST_TOKEN_HELP
642           "\t\t\tDisplay help."
643           INDENT VCL_TEST_TOKEN_EXIT
644           "\t\t\tExit test client & server."
645           INDENT VCL_TEST_TOKEN_SHOW_CFG
646           "\t\t\tShow the current test cfg."
647           INDENT VCL_TEST_TOKEN_RUN_UNI
648           "\t\t\tRun the Uni-directional test."
649           INDENT VCL_TEST_TOKEN_RUN_BI
650           "\t\t\tRun the Bi-directional test."
651           INDENT VCL_TEST_TOKEN_VERBOSE
652           "\t\t\tToggle verbose setting."
653           INDENT VCL_TEST_TOKEN_RXBUF_SIZE
654           "<rxbuf size>\tRx buffer size (bytes)."
655           INDENT VCL_TEST_TOKEN_TXBUF_SIZE
656           "<txbuf size>\tTx buffer size (bytes)."
657           INDENT VCL_TEST_TOKEN_NUM_WRITES
658           "<# of writes>\tNumber of txbuf writes to server." "\n");
659 }
660
661 static void
662 cfg_txbuf_size_set (void)
663 {
664   vcl_test_client_main_t *vcm = &vcl_client_main;
665   vcl_test_session_t *ctrl = &vcm->ctrl_session;
666   char *p = ctrl->txbuf + strlen (VCL_TEST_TOKEN_TXBUF_SIZE);
667   uint64_t txbuf_size = strtoull ((const char *) p, NULL, 10);
668
669   if (txbuf_size >= VCL_TEST_CFG_BUF_SIZE_MIN)
670     {
671       ctrl->cfg.txbuf_size = txbuf_size;
672       ctrl->cfg.total_bytes = ctrl->cfg.num_writes * ctrl->cfg.txbuf_size;
673       vcl_test_buf_alloc (&ctrl->cfg, 0 /* is_rxbuf */ ,
674                           (uint8_t **) & ctrl->txbuf, &ctrl->txbuf_size);
675       vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
676     }
677   else
678     vtwrn ("Invalid txbuf size (%lu) < minimum buf size (%u)!",
679            txbuf_size, VCL_TEST_CFG_BUF_SIZE_MIN);
680 }
681
682 static void
683 cfg_num_writes_set (void)
684 {
685   vcl_test_client_main_t *vcm = &vcl_client_main;
686   vcl_test_session_t *ctrl = &vcm->ctrl_session;
687   char *p = ctrl->txbuf + strlen (VCL_TEST_TOKEN_NUM_WRITES);
688   uint32_t num_writes = strtoul ((const char *) p, NULL, 10);
689
690   if (num_writes > 0)
691     {
692       ctrl->cfg.num_writes = num_writes;
693       ctrl->cfg.total_bytes = ctrl->cfg.num_writes * ctrl->cfg.txbuf_size;
694       vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
695     }
696   else
697     {
698       vtwrn ("invalid num writes: %u", num_writes);
699     }
700 }
701
702 static void
703 cfg_num_test_sessions_set (void)
704 {
705   vcl_test_client_main_t *vcm = &vcl_client_main;
706   vcl_test_session_t *ctrl = &vcm->ctrl_session;
707   char *p = ctrl->txbuf + strlen (VCL_TEST_TOKEN_NUM_TEST_SESS);
708   uint32_t num_test_sessions = strtoul ((const char *) p, NULL, 10);
709
710   if ((num_test_sessions > 0) &&
711       (num_test_sessions <= VCL_TEST_CFG_MAX_TEST_SESS))
712     {
713       ctrl->cfg.num_test_sessions = num_test_sessions;
714       vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
715     }
716   else
717     {
718       vtwrn ("invalid num test sessions: %u, (%d max)",
719              num_test_sessions, VCL_TEST_CFG_MAX_TEST_SESS);
720     }
721 }
722
723 static void
724 cfg_rxbuf_size_set (void)
725 {
726   vcl_test_client_main_t *vcm = &vcl_client_main;
727   vcl_test_session_t *ctrl = &vcm->ctrl_session;
728   char *p = ctrl->txbuf + strlen (VCL_TEST_TOKEN_RXBUF_SIZE);
729   uint64_t rxbuf_size = strtoull ((const char *) p, NULL, 10);
730
731   if (rxbuf_size >= VCL_TEST_CFG_BUF_SIZE_MIN)
732     {
733       ctrl->cfg.rxbuf_size = rxbuf_size;
734       vcl_test_buf_alloc (&ctrl->cfg, 1 /* is_rxbuf */ ,
735                           (uint8_t **) & ctrl->rxbuf, &ctrl->rxbuf_size);
736       vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
737     }
738   else
739     vtwrn ("Invalid rxbuf size (%lu) < minimum buf size (%u)!",
740            rxbuf_size, VCL_TEST_CFG_BUF_SIZE_MIN);
741 }
742
743 static void
744 cfg_verbose_toggle (void)
745 {
746   vcl_test_client_main_t *vcm = &vcl_client_main;
747   vcl_test_session_t *ctrl = &vcm->ctrl_session;
748
749   ctrl->cfg.verbose = ctrl->cfg.verbose ? 0 : 1;
750   vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
751
752 }
753
754 static vcl_test_t
755 parse_input ()
756 {
757   vcl_test_client_main_t *vcm = &vcl_client_main;
758   vcl_test_session_t *ctrl = &vcm->ctrl_session;
759   vcl_test_t rv = VCL_TEST_TYPE_NONE;
760
761   if (!strncmp (VCL_TEST_TOKEN_EXIT, ctrl->txbuf,
762                 strlen (VCL_TEST_TOKEN_EXIT)))
763     rv = VCL_TEST_TYPE_EXIT;
764
765   else if (!strncmp (VCL_TEST_TOKEN_HELP, ctrl->txbuf,
766                      strlen (VCL_TEST_TOKEN_HELP)))
767     dump_help ();
768
769   else if (!strncmp (VCL_TEST_TOKEN_SHOW_CFG, ctrl->txbuf,
770                      strlen (VCL_TEST_TOKEN_SHOW_CFG)))
771     vcm->dump_cfg = 1;
772
773   else if (!strncmp (VCL_TEST_TOKEN_VERBOSE, ctrl->txbuf,
774                      strlen (VCL_TEST_TOKEN_VERBOSE)))
775     cfg_verbose_toggle ();
776
777   else if (!strncmp (VCL_TEST_TOKEN_TXBUF_SIZE, ctrl->txbuf,
778                      strlen (VCL_TEST_TOKEN_TXBUF_SIZE)))
779     cfg_txbuf_size_set ();
780
781   else if (!strncmp (VCL_TEST_TOKEN_NUM_TEST_SESS, ctrl->txbuf,
782                      strlen (VCL_TEST_TOKEN_NUM_TEST_SESS)))
783     cfg_num_test_sessions_set ();
784
785   else if (!strncmp (VCL_TEST_TOKEN_NUM_WRITES, ctrl->txbuf,
786                      strlen (VCL_TEST_TOKEN_NUM_WRITES)))
787     cfg_num_writes_set ();
788
789   else if (!strncmp (VCL_TEST_TOKEN_RXBUF_SIZE, ctrl->txbuf,
790                      strlen (VCL_TEST_TOKEN_RXBUF_SIZE)))
791     cfg_rxbuf_size_set ();
792
793   else if (!strncmp (VCL_TEST_TOKEN_RUN_UNI, ctrl->txbuf,
794                      strlen (VCL_TEST_TOKEN_RUN_UNI)))
795     rv = ctrl->cfg.test = VCL_TEST_TYPE_UNI;
796
797   else if (!strncmp (VCL_TEST_TOKEN_RUN_BI, ctrl->txbuf,
798                      strlen (VCL_TEST_TOKEN_RUN_BI)))
799     rv = ctrl->cfg.test = VCL_TEST_TYPE_BI;
800
801   else
802     rv = VCL_TEST_TYPE_ECHO;
803
804   return rv;
805 }
806
807 void
808 print_usage_and_exit (void)
809 {
810   fprintf (stderr,
811            "vcl_test_client [OPTIONS] <ipaddr> <port>\n"
812            "  OPTIONS\n"
813            "  -h               Print this message and exit.\n"
814            "  -6               Use IPv6\n"
815            "  -c               Print test config before test.\n"
816            "  -w <dir>         Write test results to <dir>.\n"
817            "  -X               Exit after running test.\n"
818            "  -p <proto>       Use <proto> transport layer\n"
819            "  -D               Use UDP transport layer\n"
820            "  -L               Use TLS transport layer\n"
821            "  -E               Run Echo test.\n"
822            "  -N <num-writes>  Test Cfg: number of writes.\n"
823            "  -R <rxbuf-size>  Test Cfg: rx buffer size.\n"
824            "  -T <txbuf-size>  Test Cfg: tx buffer size.\n"
825            "  -U               Run Uni-directional test.\n"
826            "  -B               Run Bi-directional test.\n"
827            "  -V               Verbose mode.\n"
828            "  -I <N>           Use N sessions.\n"
829            "  -s <N>           Use N sessions.\n"
830            "  -q <n>           QUIC : use N Ssessions on top of n Qsessions\n");
831   exit (1);
832 }
833
834 static void
835 vtc_process_opts (vcl_test_client_main_t * vcm, int argc, char **argv)
836 {
837   vcl_test_session_t *ctrl = &vcm->ctrl_session;
838   int c, v;
839
840   opterr = 0;
841   while ((c = getopt (argc, argv, "chnp:w:XE:I:N:R:T:UBV6DLs:q:")) != -1)
842     switch (c)
843       {
844       case 'c':
845         vcm->dump_cfg = 1;
846         break;
847
848       case 'I':         /* deprecated */
849       case 's':
850         if (sscanf (optarg, "0x%x", &ctrl->cfg.num_test_sessions) != 1)
851           if (sscanf (optarg, "%u", &ctrl->cfg.num_test_sessions) != 1)
852             {
853               vtwrn ("Invalid value for option -%c!", c);
854               print_usage_and_exit ();
855             }
856         if (!ctrl->cfg.num_test_sessions ||
857             (ctrl->cfg.num_test_sessions > VCL_TEST_CFG_MAX_TEST_SESS))
858           {
859             vtwrn ("Invalid number of sessions (%d) specified for option -%c!"
860                    "\n       Valid range is 1 - %d",
861                    ctrl->cfg.num_test_sessions, c,
862                    VCL_TEST_CFG_MAX_TEST_SESS);
863             print_usage_and_exit ();
864           }
865         break;
866
867       case 'q':
868         if (sscanf (optarg, "0x%x", &ctrl->cfg.num_test_sessions_perq) != 1)
869           if (sscanf (optarg, "%u", &ctrl->cfg.num_test_sessions_perq) != 1)
870             {
871               vtwrn ("Invalid value for option -%c!", c);
872               print_usage_and_exit ();
873             }
874         if (!ctrl->cfg.num_test_sessions_perq ||
875             (ctrl->cfg.num_test_sessions_perq > VCL_TEST_CFG_MAX_TEST_SESS))
876           {
877             vtwrn ("Invalid number of Stream sessions (%d) per Qsession"
878                    "for option -%c!\nValid range is 1 - %d",
879                    ctrl->cfg.num_test_sessions_perq, c,
880                    VCL_TEST_CFG_MAX_TEST_SESS);
881             print_usage_and_exit ();
882           }
883         break;
884
885       case 'w':
886         if (sscanf (optarg, "%d", &v) != 1)
887           {
888             vtwrn ("Invalid value for option -%c!", c);
889             print_usage_and_exit ();
890           }
891         if (v > 1)
892           vcm->n_workers = v;
893         break;
894
895       case 'X':
896         vcm->post_test = VCL_TEST_TYPE_EXIT;
897         break;
898
899       case 'E':
900         if (strlen (optarg) > ctrl->txbuf_size)
901           {
902             vtwrn ("Option -%c value larger than txbuf size (%d)!",
903                    optopt, ctrl->txbuf_size);
904             print_usage_and_exit ();
905           }
906         strcpy (ctrl->txbuf, optarg);
907         ctrl->cfg.test = VCL_TEST_TYPE_ECHO;
908         break;
909
910       case 'N':
911         if (sscanf (optarg, "0x%lx", &ctrl->cfg.num_writes) != 1)
912           if (sscanf (optarg, "%ld", &ctrl->cfg.num_writes) != 1)
913             {
914               vtwrn ("Invalid value for option -%c!", c);
915               print_usage_and_exit ();
916             }
917         ctrl->cfg.total_bytes = ctrl->cfg.num_writes * ctrl->cfg.txbuf_size;
918         break;
919
920       case 'R':
921         if (sscanf (optarg, "0x%lx", &ctrl->cfg.rxbuf_size) != 1)
922           if (sscanf (optarg, "%ld", &ctrl->cfg.rxbuf_size) != 1)
923             {
924               vtwrn ("Invalid value for option -%c!", c);
925               print_usage_and_exit ();
926             }
927         if (ctrl->cfg.rxbuf_size >= VCL_TEST_CFG_BUF_SIZE_MIN)
928           {
929             ctrl->rxbuf_size = ctrl->cfg.rxbuf_size;
930             vcl_test_buf_alloc (&ctrl->cfg, 1 /* is_rxbuf */ ,
931                                 (uint8_t **) & ctrl->rxbuf,
932                                 &ctrl->rxbuf_size);
933           }
934         else
935           {
936             vtwrn ("rxbuf size (%lu) less than minumum (%u)",
937                    ctrl->cfg.rxbuf_size, VCL_TEST_CFG_BUF_SIZE_MIN);
938             print_usage_and_exit ();
939           }
940
941         break;
942
943       case 'T':
944         if (sscanf (optarg, "0x%lx", &ctrl->cfg.txbuf_size) != 1)
945           if (sscanf (optarg, "%ld", &ctrl->cfg.txbuf_size) != 1)
946             {
947               vtwrn ("Invalid value for option -%c!", c);
948               print_usage_and_exit ();
949             }
950         if (ctrl->cfg.txbuf_size >= VCL_TEST_CFG_BUF_SIZE_MIN)
951           {
952             ctrl->txbuf_size = ctrl->cfg.txbuf_size;
953             vcl_test_buf_alloc (&ctrl->cfg, 0 /* is_rxbuf */ ,
954                                 (uint8_t **) & ctrl->txbuf,
955                                 &ctrl->txbuf_size);
956             ctrl->cfg.total_bytes =
957               ctrl->cfg.num_writes * ctrl->cfg.txbuf_size;
958           }
959         else
960           {
961             vtwrn ("txbuf size (%lu) less than minumum (%u)!",
962                    ctrl->cfg.txbuf_size, VCL_TEST_CFG_BUF_SIZE_MIN);
963             print_usage_and_exit ();
964           }
965         break;
966
967       case 'U':
968         ctrl->cfg.test = VCL_TEST_TYPE_UNI;
969         break;
970
971       case 'B':
972         ctrl->cfg.test = VCL_TEST_TYPE_BI;
973         break;
974
975       case 'V':
976         ctrl->cfg.verbose = 1;
977         break;
978
979       case '6':
980         ctrl->cfg.address_ip6 = 1;
981         break;
982
983       case 'p':
984         if (vppcom_unformat_proto (&vcm->proto, optarg))
985           vtwrn ("Invalid vppcom protocol %s, defaulting to TCP", optarg);
986         break;
987
988       case 'D':         /* deprecated */
989         vcm->proto = VPPCOM_PROTO_UDP;
990         break;
991
992       case 'L':         /* deprecated */
993         vcm->proto = VPPCOM_PROTO_TLS;
994         break;
995
996       case '?':
997         switch (optopt)
998           {
999           case 'E':
1000           case 'I':             /* deprecated */
1001           case 'N':
1002           case 'R':
1003           case 'T':
1004           case 'w':
1005           case 'p':
1006           case 'q':
1007             vtwrn ("Option -%c requires an argument.", optopt);
1008             break;
1009
1010           default:
1011             if (isprint (optopt))
1012               vtwrn ("Unknown option `-%c'.", optopt);
1013             else
1014               vtwrn ("Unknown option character `\\x%x'.", optopt);
1015           }
1016         /* fall thru */
1017       case 'h':
1018       default:
1019         print_usage_and_exit ();
1020       }
1021
1022   if (argc < (optind + 2))
1023     {
1024       vtwrn ("Insufficient number of arguments!");
1025       print_usage_and_exit ();
1026     }
1027
1028   ctrl->cfg.num_test_qsessions = vcm->proto != VPPCOM_PROTO_QUIC ? 0 :
1029     (ctrl->cfg.num_test_sessions + ctrl->cfg.num_test_sessions_perq - 1) /
1030     ctrl->cfg.num_test_sessions_perq;
1031
1032   memset (&vcm->server_addr, 0, sizeof (vcm->server_addr));
1033   if (ctrl->cfg.address_ip6)
1034     {
1035       struct sockaddr_in6 *sddr6 = (struct sockaddr_in6 *) &vcm->server_addr;
1036       sddr6->sin6_family = AF_INET6;
1037       inet_pton (AF_INET6, argv[optind++], &(sddr6->sin6_addr));
1038       sddr6->sin6_port = htons (atoi (argv[optind]));
1039
1040       vcm->server_endpt.is_ip4 = 0;
1041       vcm->server_endpt.ip = (uint8_t *) & sddr6->sin6_addr;
1042       vcm->server_endpt.port = (uint16_t) sddr6->sin6_port;
1043     }
1044   else
1045     {
1046       struct sockaddr_in *saddr4 = (struct sockaddr_in *) &vcm->server_addr;
1047       saddr4->sin_family = AF_INET;
1048       inet_pton (AF_INET, argv[optind++], &(saddr4->sin_addr));
1049       saddr4->sin_port = htons (atoi (argv[optind]));
1050
1051       vcm->server_endpt.is_ip4 = 1;
1052       vcm->server_endpt.ip = (uint8_t *) & saddr4->sin_addr;
1053       vcm->server_endpt.port = (uint16_t) saddr4->sin_port;
1054     }
1055 }
1056
1057 static void
1058 vtc_read_user_input (vcl_test_session_t * ctrl)
1059 {
1060   printf ("\nType some characters and hit <return>\n"
1061           "('" VCL_TEST_TOKEN_HELP "' for help): ");
1062
1063   if (fgets (ctrl->txbuf, ctrl->txbuf_size, stdin) != NULL)
1064     {
1065       if (strlen (ctrl->txbuf) == 1)
1066         {
1067           printf ("\nNothing to send!  Please try again...\n");
1068           return;
1069         }
1070       ctrl->txbuf[strlen (ctrl->txbuf) - 1] = 0;        // chomp the newline.
1071
1072       /* Parse input for keywords */
1073       ctrl->cfg.test = parse_input ();
1074     }
1075 }
1076
1077 static void
1078 vtc_ctrl_session_exit (void)
1079 {
1080   vcl_test_client_main_t *vcm = &vcl_client_main;
1081   vcl_test_session_t *ctrl = &vcm->ctrl_session;
1082   int verbose = ctrl->cfg.verbose;
1083
1084   ctrl->cfg.test = VCL_TEST_TYPE_EXIT;
1085   if (verbose)
1086     {
1087       vtinf ("(fd %d): Sending exit cfg to server...", ctrl->fd);
1088       vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
1089     }
1090   (void) vcl_test_write (ctrl->fd, (uint8_t *) & ctrl->cfg,
1091                          sizeof (ctrl->cfg), &ctrl->stats, verbose);
1092   sleep (1);
1093 }
1094
1095 int
1096 main (int argc, char **argv)
1097 {
1098   vcl_test_client_main_t *vcm = &vcl_client_main;
1099   vcl_test_session_t *ctrl = &vcm->ctrl_session;
1100   vcl_test_session_t *quic_session = &vcm->quic_session;
1101   int rv;
1102
1103   vcm->n_workers = 1;
1104   vcl_test_cfg_init (&ctrl->cfg);
1105   vcl_test_session_buf_alloc (ctrl);
1106   vtc_process_opts (vcm, argc, argv);
1107
1108   vcm->workers = calloc (vcm->n_workers, sizeof (vcl_test_client_worker_t));
1109   rv = vppcom_app_create ("vcl_test_client");
1110   if (rv < 0)
1111     vtfail ("vppcom_app_create()", rv);
1112
1113   ctrl->fd = vppcom_session_create (vcm->proto, 0 /* is_nonblocking */ );
1114   if (ctrl->fd < 0)
1115     vtfail ("vppcom_session_create()", ctrl->fd);
1116
1117   if (vcm->proto == VPPCOM_PROTO_TLS || vcm->proto == VPPCOM_PROTO_QUIC)
1118     {
1119       vtinf ("Adding tls certs ...");
1120       vppcom_session_tls_add_cert (ctrl->fd, vcl_test_crt_rsa,
1121                                    vcl_test_crt_rsa_len);
1122       vppcom_session_tls_add_key (ctrl->fd, vcl_test_key_rsa,
1123                                   vcl_test_key_rsa_len);
1124     }
1125
1126   vtinf ("Connecting to server...");
1127   if (vcm->proto == VPPCOM_PROTO_QUIC)
1128     {
1129       quic_session->fd = vppcom_session_create (vcm->proto,
1130                                                 0 /* is_nonblocking */ );
1131       if (quic_session->fd < 0)
1132         vtfail ("vppcom_session_create()", quic_session->fd);
1133       rv = vppcom_session_connect (quic_session->fd, &vcm->server_endpt);
1134       if (rv)
1135         vtfail ("vppcom_session_connect()", rv);
1136       vtinf ("Connecting to stream...");
1137       rv = vppcom_session_stream_connect (ctrl->fd, quic_session->fd);
1138     }
1139   else
1140     rv = vppcom_session_connect (ctrl->fd, &vcm->server_endpt);
1141   if (rv)
1142     vtfail ("vppcom_session_connect()", rv);
1143   vtinf ("Control session (fd %d) connected.", ctrl->fd);
1144
1145   rv = vtc_cfg_sync (ctrl);
1146   if (rv)
1147     vtfail ("vtc_cfg_sync()", rv);
1148
1149   ctrl->cfg.ctrl_handle = ((vcl_test_cfg_t *) ctrl->rxbuf)->ctrl_handle;
1150   memset (&ctrl->stats, 0, sizeof (ctrl->stats));
1151
1152   while (ctrl->cfg.test != VCL_TEST_TYPE_EXIT)
1153     {
1154       if (vcm->dump_cfg)
1155         {
1156           vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
1157           vcm->dump_cfg = 0;
1158         }
1159
1160       switch (ctrl->cfg.test)
1161         {
1162         case VCL_TEST_TYPE_ECHO:
1163           vtc_echo_client (vcm);
1164           break;
1165
1166         case VCL_TEST_TYPE_UNI:
1167         case VCL_TEST_TYPE_BI:
1168           vtc_stream_client (vcm);
1169           break;
1170
1171         case VCL_TEST_TYPE_EXIT:
1172           continue;
1173
1174         case VCL_TEST_TYPE_NONE:
1175         default:
1176           break;
1177         }
1178       switch (vcm->post_test)
1179         {
1180         case VCL_TEST_TYPE_EXIT:
1181           switch (ctrl->cfg.test)
1182             {
1183             case VCL_TEST_TYPE_EXIT:
1184             case VCL_TEST_TYPE_UNI:
1185             case VCL_TEST_TYPE_BI:
1186             case VCL_TEST_TYPE_ECHO:
1187               ctrl->cfg.test = VCL_TEST_TYPE_EXIT;
1188               continue;
1189
1190             case VCL_TEST_TYPE_NONE:
1191             default:
1192               break;
1193             }
1194           break;
1195
1196         case VCL_TEST_TYPE_NONE:
1197         case VCL_TEST_TYPE_ECHO:
1198         case VCL_TEST_TYPE_UNI:
1199         case VCL_TEST_TYPE_BI:
1200         default:
1201           break;
1202         }
1203
1204       memset (ctrl->txbuf, 0, ctrl->txbuf_size);
1205       memset (ctrl->rxbuf, 0, ctrl->rxbuf_size);
1206
1207       vtc_read_user_input (ctrl);
1208     }
1209
1210   vtc_ctrl_session_exit ();
1211   if (quic_session)
1212     vppcom_session_close (quic_session->fd);
1213   vppcom_app_destroy ();
1214   free (vcm->workers);
1215   return 0;
1216 }
1217
1218 /*
1219  * fd.io coding-style-patch-verification: ON
1220  *
1221  * Local Variables:
1222  * eval: (c-set-style "gnu")
1223  * End:
1224  */