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