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