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