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