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