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