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