vcl session: switch to generic cert key apis
[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)
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   vtinf ("Sending config to server on all sessions ...");
369
370   for (n = 0; n < cfg->num_test_sessions; n++)
371     {
372       ts = &wrk->sessions[n];
373       if (vtc_cfg_sync (ts))
374         return -1;
375       memset (&ts->stats, 0, sizeof (ts->stats));
376     }
377
378   return 0;
379 }
380
381 static int stats_lock = 0;
382
383 static void
384 vtc_accumulate_stats (vcl_test_client_worker_t * wrk,
385                       vcl_test_session_t * ctrl)
386 {
387   vcl_test_session_t *ts;
388   static char buf[64];
389   int i, show_rx = 0;
390
391   while (__sync_lock_test_and_set (&stats_lock, 1))
392     ;
393
394   if (ctrl->cfg.test == VCL_TEST_TYPE_BI
395       || ctrl->cfg.test == VCL_TEST_TYPE_ECHO)
396     show_rx = 1;
397
398   for (i = 0; i < wrk->cfg.num_test_sessions; i++)
399     {
400       ts = &wrk->sessions[i];
401       ts->stats.start = ctrl->stats.start;
402
403       if (ctrl->cfg.verbose > 1)
404         {
405           snprintf (buf, sizeof (buf), "CLIENT (fd %d) RESULTS", ts->fd);
406           vcl_test_stats_dump (buf, &ts->stats, show_rx, 1 /* show tx */ ,
407                                ctrl->cfg.verbose);
408         }
409
410       vcl_test_stats_accumulate (&ctrl->stats, &ts->stats);
411       if (vcl_comp_tspec (&ctrl->stats.stop, &ts->stats.stop) < 0)
412         ctrl->stats.stop = ts->stats.stop;
413     }
414
415   __sync_lock_release (&stats_lock);
416 }
417
418 static void
419 vtc_worker_sessions_exit (vcl_test_client_worker_t * wrk)
420 {
421   vcl_test_client_main_t *vcm = &vcl_client_main;
422   vcl_test_session_t *ctrl = &vcm->ctrl_session;
423   vcl_test_session_t *ts;
424   int i, verbose = ctrl->cfg.verbose;
425
426   for (i = 0; i < wrk->cfg.num_test_sessions; i++)
427     {
428       ts = &wrk->sessions[i];
429       ts->cfg.test = VCL_TEST_TYPE_EXIT;
430
431       if (verbose)
432         {
433           vtinf ("(fd %d): Sending exit cfg to server...", ts->fd);
434           vcl_test_cfg_dump (&ts->cfg, 1 /* is_client */ );
435         }
436       (void) vcl_test_write (ts->fd, (uint8_t *) & ts->cfg,
437                              sizeof (ts->cfg), &ts->stats, verbose);
438     }
439
440   wrk->n_sessions = 0;
441 }
442
443 static void *
444 vtc_worker_loop (void *arg)
445 {
446   vcl_test_client_main_t *vcm = &vcl_client_main;
447   vcl_test_session_t *ctrl = &vcm->ctrl_session;
448   vcl_test_client_worker_t *wrk = arg;
449   uint32_t n_active_sessions, n_bytes;
450   fd_set _wfdset, *wfdset = &_wfdset;
451   fd_set _rfdset, *rfdset = &_rfdset;
452   vcl_test_session_t *ts;
453   int i, rv, check_rx = 0;
454
455   rv = vtc_worker_init (wrk);
456   if (rv)
457     {
458       vterr ("vtc_worker_init()", rv);
459       return 0;
460     }
461
462   vtinf ("Starting test ...");
463
464   if (wrk->wrk_index == 0)
465     clock_gettime (CLOCK_REALTIME, &ctrl->stats.start);
466
467   check_rx = wrk->cfg.test != VCL_TEST_TYPE_UNI;
468   n_active_sessions = wrk->cfg.num_test_sessions;
469   while (n_active_sessions)
470     {
471       _wfdset = wrk->wr_fdset;
472       _rfdset = wrk->rd_fdset;
473
474       rv = vppcom_select (wrk->max_fd_index, (unsigned long *) rfdset,
475                           (unsigned long *) wfdset, NULL, 0);
476       if (rv < 0)
477         {
478           vterr ("vppcom_select()", rv);
479           goto exit;
480         }
481       else if (rv == 0)
482         continue;
483
484       for (i = 0; i < wrk->cfg.num_test_sessions; i++)
485         {
486           ts = &wrk->sessions[i];
487           if (!((ts->stats.stop.tv_sec == 0) &&
488                 (ts->stats.stop.tv_nsec == 0)))
489             continue;
490
491           if (FD_ISSET (vppcom_session_index (ts->fd), rfdset)
492               && ts->stats.rx_bytes < ts->cfg.total_bytes)
493             {
494               (void) vcl_test_read (ts->fd, (uint8_t *) ts->rxbuf,
495                                     ts->rxbuf_size, &ts->stats);
496             }
497
498           if (FD_ISSET (vppcom_session_index (ts->fd), wfdset)
499               && ts->stats.tx_bytes < ts->cfg.total_bytes)
500             {
501               n_bytes = ts->cfg.txbuf_size;
502               if (ts->cfg.test == VCL_TEST_TYPE_ECHO)
503                 n_bytes = strlen (ctrl->txbuf) + 1;
504               rv = vcl_test_write (ts->fd, (uint8_t *) ts->txbuf,
505                                    n_bytes, &ts->stats, ts->cfg.verbose);
506               if (rv < 0)
507                 {
508                   vtwrn ("vppcom_test_write (%d) failed -- aborting test",
509                          ts->fd);
510                   goto exit;
511                 }
512             }
513           if ((!check_rx && ts->stats.tx_bytes >= ts->cfg.total_bytes)
514               || (check_rx && ts->stats.rx_bytes >= ts->cfg.total_bytes))
515             {
516               clock_gettime (CLOCK_REALTIME, &ts->stats.stop);
517               n_active_sessions--;
518             }
519         }
520     }
521 exit:
522   vtinf ("Worker %d done ...", wrk->wrk_index);
523   if (wrk->cfg.test != VCL_TEST_TYPE_ECHO)
524     vtc_accumulate_stats (wrk, ctrl);
525   sleep (VCL_TEST_DELAY_DISCONNECT);
526   vtc_worker_sessions_exit (wrk);
527   if (wrk->wrk_index)
528     vt_atomic_add (&vcm->active_workers, -1);
529   return 0;
530 }
531
532 static void
533 vtc_print_stats (vcl_test_session_t * ctrl)
534 {
535   int is_echo = ctrl->cfg.test == VCL_TEST_TYPE_ECHO;
536   int show_rx = 0;
537   char buf[64];
538
539   if (ctrl->cfg.test == VCL_TEST_TYPE_BI
540       || ctrl->cfg.test == VCL_TEST_TYPE_ECHO)
541     show_rx = 1;
542
543   vcl_test_stats_dump ("CLIENT RESULTS", &ctrl->stats,
544                        show_rx, 1 /* show tx */ ,
545                        ctrl->cfg.verbose);
546   vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
547
548   if (ctrl->cfg.verbose)
549     {
550       vtinf ("  ctrl session info\n"
551              VCL_TEST_SEPARATOR_STRING
552              "          fd:  %d (0x%08x)\n"
553              "       rxbuf:  %p\n"
554              "  rxbuf size:  %u (0x%08x)\n"
555              "       txbuf:  %p\n"
556              "  txbuf size:  %u (0x%08x)\n"
557              VCL_TEST_SEPARATOR_STRING,
558              ctrl->fd, (uint32_t) ctrl->fd,
559              ctrl->rxbuf, ctrl->rxbuf_size, ctrl->rxbuf_size,
560              ctrl->txbuf, ctrl->txbuf_size, ctrl->txbuf_size);
561     }
562
563   if (is_echo)
564     snprintf (buf, sizeof (buf), "Echo");
565   else
566     snprintf (buf, sizeof (buf), "%s-directional Stream",
567               ctrl->cfg.test == VCL_TEST_TYPE_BI ? "Bi" : "Uni");
568 }
569
570 static void
571 vtc_echo_client (vcl_test_client_main_t * vcm)
572 {
573   vcl_test_client_worker_t *wrk;
574   vcl_test_session_t *ctrl = &vcm->ctrl_session;
575   vcl_test_cfg_t *cfg = &ctrl->cfg;
576
577   cfg->total_bytes = strlen (ctrl->txbuf) + 1;
578   memset (&ctrl->stats, 0, sizeof (ctrl->stats));
579
580   /* Echo works with only one worker */
581   wrk = vcm->workers;
582   wrk->wrk_index = 0;
583   wrk->cfg = *cfg;
584
585   vtc_worker_loop (wrk);
586
587   /* Not relevant for echo test
588      clock_gettime (CLOCK_REALTIME, &ctrl->stats.stop);
589      vtc_accumulate_stats (wrk, ctrl);
590      vtc_print_stats (ctrl);
591    */
592 }
593
594 static void
595 vtc_stream_client (vcl_test_client_main_t * vcm)
596 {
597   vcl_test_session_t *ctrl = &vcm->ctrl_session;
598   vcl_test_cfg_t *cfg = &ctrl->cfg;
599   vcl_test_client_worker_t *wrk;
600   uint32_t i, n_conn, n_conn_per_wrk;
601
602   vtinf ("%s-directional Stream Test Starting!",
603          ctrl->cfg.test == VCL_TEST_TYPE_BI ? "Bi" : "Uni");
604
605   cfg->total_bytes = cfg->num_writes * cfg->txbuf_size;
606   cfg->ctrl_handle = ~0;
607   if (vtc_cfg_sync (ctrl))
608     {
609       vtwrn ("test cfg sync failed -- aborting!");
610       return;
611     }
612   cfg->ctrl_handle = ((vcl_test_cfg_t *) ctrl->rxbuf)->ctrl_handle;
613   memset (&ctrl->stats, 0, sizeof (ctrl->stats));
614
615   n_conn = cfg->num_test_sessions;
616   n_conn_per_wrk = n_conn / vcm->n_workers;
617   for (i = 0; i < vcm->n_workers; i++)
618     {
619       wrk = &vcm->workers[i];
620       wrk->wrk_index = i;
621       wrk->cfg = ctrl->cfg;
622       wrk->cfg.num_test_sessions = vtc_min (n_conn_per_wrk, n_conn);
623       n_conn -= wrk->cfg.num_test_sessions;
624     }
625
626   for (i = 1; i < vcm->n_workers; i++)
627     {
628       wrk = &vcm->workers[i];
629       pthread_create (&wrk->thread_handle, NULL, vtc_worker_loop,
630                       (void *) wrk);
631     }
632   vtc_worker_loop (&vcm->workers[0]);
633
634   while (vcm->active_workers > 0)
635     ;
636
637   vtinf ("Sending config on ctrl session (fd %d) for stats...", ctrl->fd);
638   if (vtc_cfg_sync (ctrl))
639     {
640       vtwrn ("test cfg sync failed -- aborting!");
641       return;
642     }
643
644   vtc_print_stats (ctrl);
645
646   ctrl->cfg.test = VCL_TEST_TYPE_ECHO;
647   ctrl->cfg.total_bytes = 0;
648   if (vtc_cfg_sync (ctrl))
649     vtwrn ("post-test cfg sync failed!");
650 }
651
652 static void
653 cfg_txbuf_size_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_TXBUF_SIZE);
658   uint64_t txbuf_size = strtoull ((const char *) p, NULL, 10);
659
660   if (txbuf_size >= VCL_TEST_CFG_BUF_SIZE_MIN)
661     {
662       ctrl->cfg.txbuf_size = txbuf_size;
663       ctrl->cfg.total_bytes = ctrl->cfg.num_writes * ctrl->cfg.txbuf_size;
664       vcl_test_buf_alloc (&ctrl->cfg, 0 /* is_rxbuf */ ,
665                           (uint8_t **) & ctrl->txbuf, &ctrl->txbuf_size);
666       vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
667     }
668   else
669     vtwrn ("Invalid txbuf size (%lu) < minimum buf size (%u)!",
670            txbuf_size, VCL_TEST_CFG_BUF_SIZE_MIN);
671 }
672
673 static void
674 cfg_num_writes_set (void)
675 {
676   vcl_test_client_main_t *vcm = &vcl_client_main;
677   vcl_test_session_t *ctrl = &vcm->ctrl_session;
678   char *p = ctrl->txbuf + strlen (VCL_TEST_TOKEN_NUM_WRITES);
679   uint32_t num_writes = strtoul ((const char *) p, NULL, 10);
680
681   if (num_writes > 0)
682     {
683       ctrl->cfg.num_writes = num_writes;
684       ctrl->cfg.total_bytes = ctrl->cfg.num_writes * ctrl->cfg.txbuf_size;
685       vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
686     }
687   else
688     {
689       vtwrn ("invalid num writes: %u", num_writes);
690     }
691 }
692
693 static void
694 cfg_num_test_sessions_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_NUM_TEST_SESS);
699   uint32_t num_test_sessions = strtoul ((const char *) p, NULL, 10);
700
701   if ((num_test_sessions > 0) &&
702       (num_test_sessions <= VCL_TEST_CFG_MAX_TEST_SESS))
703     {
704       ctrl->cfg.num_test_sessions = num_test_sessions;
705       vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
706     }
707   else
708     {
709       vtwrn ("invalid num test sessions: %u, (%d max)",
710              num_test_sessions, VCL_TEST_CFG_MAX_TEST_SESS);
711     }
712 }
713
714 static void
715 cfg_rxbuf_size_set (void)
716 {
717   vcl_test_client_main_t *vcm = &vcl_client_main;
718   vcl_test_session_t *ctrl = &vcm->ctrl_session;
719   char *p = ctrl->txbuf + strlen (VCL_TEST_TOKEN_RXBUF_SIZE);
720   uint64_t rxbuf_size = strtoull ((const char *) p, NULL, 10);
721
722   if (rxbuf_size >= VCL_TEST_CFG_BUF_SIZE_MIN)
723     {
724       ctrl->cfg.rxbuf_size = rxbuf_size;
725       vcl_test_buf_alloc (&ctrl->cfg, 1 /* is_rxbuf */ ,
726                           (uint8_t **) & ctrl->rxbuf, &ctrl->rxbuf_size);
727       vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
728     }
729   else
730     vtwrn ("Invalid rxbuf size (%lu) < minimum buf size (%u)!",
731            rxbuf_size, VCL_TEST_CFG_BUF_SIZE_MIN);
732 }
733
734 static void
735 cfg_verbose_toggle (void)
736 {
737   vcl_test_client_main_t *vcm = &vcl_client_main;
738   vcl_test_session_t *ctrl = &vcm->ctrl_session;
739
740   ctrl->cfg.verbose = ctrl->cfg.verbose ? 0 : 1;
741   vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
742
743 }
744
745 static vcl_test_t
746 parse_input ()
747 {
748   vcl_test_client_main_t *vcm = &vcl_client_main;
749   vcl_test_session_t *ctrl = &vcm->ctrl_session;
750   vcl_test_t rv = VCL_TEST_TYPE_NONE;
751
752   if (!strncmp (VCL_TEST_TOKEN_EXIT, ctrl->txbuf,
753                 strlen (VCL_TEST_TOKEN_EXIT)))
754     rv = VCL_TEST_TYPE_EXIT;
755
756   else if (!strncmp (VCL_TEST_TOKEN_HELP, ctrl->txbuf,
757                      strlen (VCL_TEST_TOKEN_HELP)))
758     dump_help ();
759
760   else if (!strncmp (VCL_TEST_TOKEN_SHOW_CFG, ctrl->txbuf,
761                      strlen (VCL_TEST_TOKEN_SHOW_CFG)))
762     vcm->dump_cfg = 1;
763
764   else if (!strncmp (VCL_TEST_TOKEN_VERBOSE, ctrl->txbuf,
765                      strlen (VCL_TEST_TOKEN_VERBOSE)))
766     cfg_verbose_toggle ();
767
768   else if (!strncmp (VCL_TEST_TOKEN_TXBUF_SIZE, ctrl->txbuf,
769                      strlen (VCL_TEST_TOKEN_TXBUF_SIZE)))
770     cfg_txbuf_size_set ();
771
772   else if (!strncmp (VCL_TEST_TOKEN_NUM_TEST_SESS, ctrl->txbuf,
773                      strlen (VCL_TEST_TOKEN_NUM_TEST_SESS)))
774     cfg_num_test_sessions_set ();
775
776   else if (!strncmp (VCL_TEST_TOKEN_NUM_WRITES, ctrl->txbuf,
777                      strlen (VCL_TEST_TOKEN_NUM_WRITES)))
778     cfg_num_writes_set ();
779
780   else if (!strncmp (VCL_TEST_TOKEN_RXBUF_SIZE, ctrl->txbuf,
781                      strlen (VCL_TEST_TOKEN_RXBUF_SIZE)))
782     cfg_rxbuf_size_set ();
783
784   else if (!strncmp (VCL_TEST_TOKEN_RUN_UNI, ctrl->txbuf,
785                      strlen (VCL_TEST_TOKEN_RUN_UNI)))
786     rv = ctrl->cfg.test = VCL_TEST_TYPE_UNI;
787
788   else if (!strncmp (VCL_TEST_TOKEN_RUN_BI, ctrl->txbuf,
789                      strlen (VCL_TEST_TOKEN_RUN_BI)))
790     rv = ctrl->cfg.test = VCL_TEST_TYPE_BI;
791
792   else
793     rv = VCL_TEST_TYPE_ECHO;
794
795   return rv;
796 }
797
798 void
799 print_usage_and_exit (void)
800 {
801   fprintf (stderr,
802            "vcl_test_client [OPTIONS] <ipaddr> <port>\n"
803            "  OPTIONS\n"
804            "  -h               Print this message and exit.\n"
805            "  -6               Use IPv6\n"
806            "  -c               Print test config before test.\n"
807            "  -w <dir>         Write test results to <dir>.\n"
808            "  -X               Exit after running test.\n"
809            "  -p <proto>       Use <proto> transport layer\n"
810            "  -D               Use UDP transport layer\n"
811            "  -L               Use TLS transport layer\n"
812            "  -E               Run Echo test.\n"
813            "  -N <num-writes>  Test Cfg: number of writes.\n"
814            "  -R <rxbuf-size>  Test Cfg: rx buffer size.\n"
815            "  -T <txbuf-size>  Test Cfg: tx buffer size.\n"
816            "  -U               Run Uni-directional test.\n"
817            "  -B               Run Bi-directional test.\n"
818            "  -V               Verbose mode.\n"
819            "  -I <N>           Use N sessions.\n"
820            "  -s <N>           Use N sessions.\n"
821            "  -q <n>           QUIC : use N Ssessions on top of n Qsessions\n");
822   exit (1);
823 }
824
825 static void
826 vtc_process_opts (vcl_test_client_main_t * vcm, int argc, char **argv)
827 {
828   vcl_test_session_t *ctrl = &vcm->ctrl_session;
829   int c, v;
830
831   opterr = 0;
832   while ((c = getopt (argc, argv, "chnp:w:XE:I:N:R:T:UBV6DLs:q:")) != -1)
833     switch (c)
834       {
835       case 'c':
836         vcm->dump_cfg = 1;
837         break;
838
839       case 'I':         /* deprecated */
840       case 's':
841         if (sscanf (optarg, "0x%x", &ctrl->cfg.num_test_sessions) != 1)
842           if (sscanf (optarg, "%u", &ctrl->cfg.num_test_sessions) != 1)
843             {
844               vtwrn ("Invalid value for option -%c!", c);
845               print_usage_and_exit ();
846             }
847         if (!ctrl->cfg.num_test_sessions ||
848             (ctrl->cfg.num_test_sessions > VCL_TEST_CFG_MAX_TEST_SESS))
849           {
850             vtwrn ("Invalid number of sessions (%d) specified for option -%c!"
851                    "\n       Valid range is 1 - %d",
852                    ctrl->cfg.num_test_sessions, c,
853                    VCL_TEST_CFG_MAX_TEST_SESS);
854             print_usage_and_exit ();
855           }
856         break;
857
858       case 'q':
859         if (sscanf (optarg, "0x%x", &ctrl->cfg.num_test_sessions_perq) != 1)
860           if (sscanf (optarg, "%u", &ctrl->cfg.num_test_sessions_perq) != 1)
861             {
862               vtwrn ("Invalid value for option -%c!", c);
863               print_usage_and_exit ();
864             }
865         if (!ctrl->cfg.num_test_sessions_perq ||
866             (ctrl->cfg.num_test_sessions_perq > VCL_TEST_CFG_MAX_TEST_SESS))
867           {
868             vtwrn ("Invalid number of Stream sessions (%d) per Qsession"
869                    "for option -%c!\nValid range is 1 - %d",
870                    ctrl->cfg.num_test_sessions_perq, c,
871                    VCL_TEST_CFG_MAX_TEST_SESS);
872             print_usage_and_exit ();
873           }
874         break;
875
876       case 'w':
877         if (sscanf (optarg, "%d", &v) != 1)
878           {
879             vtwrn ("Invalid value for option -%c!", c);
880             print_usage_and_exit ();
881           }
882         if (v > 1)
883           vcm->n_workers = v;
884         break;
885
886       case 'X':
887         vcm->post_test = VCL_TEST_TYPE_EXIT;
888         break;
889
890       case 'E':
891         if (strlen (optarg) > ctrl->txbuf_size)
892           {
893             vtwrn ("Option -%c value larger than txbuf size (%d)!",
894                    optopt, ctrl->txbuf_size);
895             print_usage_and_exit ();
896           }
897         strncpy (ctrl->txbuf, optarg, ctrl->txbuf_size);
898         ctrl->cfg.test = VCL_TEST_TYPE_ECHO;
899         break;
900
901       case 'N':
902         if (sscanf (optarg, "0x%lx", &ctrl->cfg.num_writes) != 1)
903           if (sscanf (optarg, "%ld", &ctrl->cfg.num_writes) != 1)
904             {
905               vtwrn ("Invalid value for option -%c!", c);
906               print_usage_and_exit ();
907             }
908         ctrl->cfg.total_bytes = ctrl->cfg.num_writes * ctrl->cfg.txbuf_size;
909         break;
910
911       case 'R':
912         if (sscanf (optarg, "0x%lx", &ctrl->cfg.rxbuf_size) != 1)
913           if (sscanf (optarg, "%ld", &ctrl->cfg.rxbuf_size) != 1)
914             {
915               vtwrn ("Invalid value for option -%c!", c);
916               print_usage_and_exit ();
917             }
918         if (ctrl->cfg.rxbuf_size >= VCL_TEST_CFG_BUF_SIZE_MIN)
919           {
920             ctrl->rxbuf_size = ctrl->cfg.rxbuf_size;
921             vcl_test_buf_alloc (&ctrl->cfg, 1 /* is_rxbuf */ ,
922                                 (uint8_t **) & ctrl->rxbuf,
923                                 &ctrl->rxbuf_size);
924           }
925         else
926           {
927             vtwrn ("rxbuf size (%lu) less than minumum (%u)",
928                    ctrl->cfg.rxbuf_size, VCL_TEST_CFG_BUF_SIZE_MIN);
929             print_usage_and_exit ();
930           }
931
932         break;
933
934       case 'T':
935         if (sscanf (optarg, "0x%lx", &ctrl->cfg.txbuf_size) != 1)
936           if (sscanf (optarg, "%ld", &ctrl->cfg.txbuf_size) != 1)
937             {
938               vtwrn ("Invalid value for option -%c!", c);
939               print_usage_and_exit ();
940             }
941         if (ctrl->cfg.txbuf_size >= VCL_TEST_CFG_BUF_SIZE_MIN)
942           {
943             ctrl->txbuf_size = ctrl->cfg.txbuf_size;
944             vcl_test_buf_alloc (&ctrl->cfg, 0 /* is_rxbuf */ ,
945                                 (uint8_t **) & ctrl->txbuf,
946                                 &ctrl->txbuf_size);
947             ctrl->cfg.total_bytes =
948               ctrl->cfg.num_writes * ctrl->cfg.txbuf_size;
949           }
950         else
951           {
952             vtwrn ("txbuf size (%lu) less than minumum (%u)!",
953                    ctrl->cfg.txbuf_size, VCL_TEST_CFG_BUF_SIZE_MIN);
954             print_usage_and_exit ();
955           }
956         break;
957
958       case 'U':
959         ctrl->cfg.test = VCL_TEST_TYPE_UNI;
960         break;
961
962       case 'B':
963         ctrl->cfg.test = VCL_TEST_TYPE_BI;
964         break;
965
966       case 'V':
967         ctrl->cfg.verbose = 1;
968         break;
969
970       case '6':
971         ctrl->cfg.address_ip6 = 1;
972         break;
973
974       case 'p':
975         if (vppcom_unformat_proto (&vcm->proto, optarg))
976           vtwrn ("Invalid vppcom protocol %s, defaulting to TCP", optarg);
977         break;
978
979       case 'D':         /* deprecated */
980         vcm->proto = VPPCOM_PROTO_UDP;
981         break;
982
983       case 'L':         /* deprecated */
984         vcm->proto = VPPCOM_PROTO_TLS;
985         break;
986
987       case '?':
988         switch (optopt)
989           {
990           case 'E':
991           case 'I':             /* deprecated */
992           case 'N':
993           case 'R':
994           case 'T':
995           case 'w':
996           case 'p':
997           case 'q':
998             vtwrn ("Option -%c requires an argument.", optopt);
999             break;
1000
1001           default:
1002             if (isprint (optopt))
1003               vtwrn ("Unknown option `-%c'.", optopt);
1004             else
1005               vtwrn ("Unknown option character `\\x%x'.", optopt);
1006           }
1007         /* fall thru */
1008       case 'h':
1009       default:
1010         print_usage_and_exit ();
1011       }
1012
1013   if (argc < (optind + 2))
1014     {
1015       vtwrn ("Insufficient number of arguments!");
1016       print_usage_and_exit ();
1017     }
1018
1019   ctrl->cfg.num_test_qsessions = vcm->proto != VPPCOM_PROTO_QUIC ? 0 :
1020     (ctrl->cfg.num_test_sessions + ctrl->cfg.num_test_sessions_perq - 1) /
1021     ctrl->cfg.num_test_sessions_perq;
1022
1023   memset (&vcm->server_addr, 0, sizeof (vcm->server_addr));
1024   if (ctrl->cfg.address_ip6)
1025     {
1026       struct sockaddr_in6 *sddr6 = (struct sockaddr_in6 *) &vcm->server_addr;
1027       sddr6->sin6_family = AF_INET6;
1028       inet_pton (AF_INET6, argv[optind++], &(sddr6->sin6_addr));
1029       sddr6->sin6_port = htons (atoi (argv[optind]));
1030
1031       vcm->server_endpt.is_ip4 = 0;
1032       vcm->server_endpt.ip = (uint8_t *) & sddr6->sin6_addr;
1033       vcm->server_endpt.port = (uint16_t) sddr6->sin6_port;
1034     }
1035   else
1036     {
1037       struct sockaddr_in *saddr4 = (struct sockaddr_in *) &vcm->server_addr;
1038       saddr4->sin_family = AF_INET;
1039       inet_pton (AF_INET, argv[optind++], &(saddr4->sin_addr));
1040       saddr4->sin_port = htons (atoi (argv[optind]));
1041
1042       vcm->server_endpt.is_ip4 = 1;
1043       vcm->server_endpt.ip = (uint8_t *) & saddr4->sin_addr;
1044       vcm->server_endpt.port = (uint16_t) saddr4->sin_port;
1045     }
1046 }
1047
1048 static void
1049 vtc_read_user_input (vcl_test_session_t * ctrl)
1050 {
1051   printf ("\nType some characters and hit <return>\n"
1052           "('" VCL_TEST_TOKEN_HELP "' for help): ");
1053
1054   if (fgets (ctrl->txbuf, ctrl->txbuf_size, stdin) != NULL)
1055     {
1056       if (strlen (ctrl->txbuf) == 1)
1057         {
1058           printf ("\nNothing to send!  Please try again...\n");
1059           return;
1060         }
1061       ctrl->txbuf[strlen (ctrl->txbuf) - 1] = 0;        // chomp the newline.
1062
1063       /* Parse input for keywords */
1064       ctrl->cfg.test = parse_input ();
1065     }
1066 }
1067
1068 static void
1069 vtc_ctrl_session_exit (void)
1070 {
1071   vcl_test_client_main_t *vcm = &vcl_client_main;
1072   vcl_test_session_t *ctrl = &vcm->ctrl_session;
1073   int verbose = ctrl->cfg.verbose;
1074
1075   ctrl->cfg.test = VCL_TEST_TYPE_EXIT;
1076   if (verbose)
1077     {
1078       vtinf ("(fd %d): Sending exit cfg to server...", ctrl->fd);
1079       vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
1080     }
1081   (void) vcl_test_write (ctrl->fd, (uint8_t *) & ctrl->cfg,
1082                          sizeof (ctrl->cfg), &ctrl->stats, verbose);
1083   sleep (1);
1084 }
1085
1086 int
1087 main (int argc, char **argv)
1088 {
1089   vcl_test_client_main_t *vcm = &vcl_client_main;
1090   vcl_test_session_t *ctrl = &vcm->ctrl_session;
1091   vcl_test_session_t *quic_session = &vcm->quic_session;
1092   int rv;
1093
1094   vcm->n_workers = 1;
1095   vcl_test_cfg_init (&ctrl->cfg);
1096   vcl_test_session_buf_alloc (ctrl);
1097   vtc_process_opts (vcm, argc, argv);
1098
1099   vcm->workers = calloc (vcm->n_workers, sizeof (vcl_test_client_worker_t));
1100   rv = vppcom_app_create ("vcl_test_client");
1101   if (rv < 0)
1102     vtfail ("vppcom_app_create()", rv);
1103
1104   ctrl->fd = vppcom_session_create (vcm->proto, 0 /* is_nonblocking */ );
1105   if (ctrl->fd < 0)
1106     vtfail ("vppcom_session_create()", ctrl->fd);
1107
1108   if (vcm->proto == VPPCOM_PROTO_TLS || vcm->proto == VPPCOM_PROTO_QUIC)
1109     {
1110       vppcom_cert_key_pair_t ckpair;
1111       uint32_t ckp_len;
1112       int ckp_index;
1113
1114       vtinf ("Adding tls certs ...");
1115       ckpair.cert = vcl_test_crt_rsa;
1116       ckpair.key = vcl_test_key_rsa;
1117       ckpair.cert_len = vcl_test_crt_rsa_len;
1118       ckpair.key_len = vcl_test_key_rsa_len;
1119       ckp_index = vppcom_add_cert_key_pair (&ckpair);
1120       if (ckp_index < 0)
1121         vtfail ("vppcom_add_cert_key_pair()", ckp_index);
1122
1123       vcm->ckpair_index = ckp_index;
1124       ckp_len = sizeof (ckp_index);
1125       vppcom_session_attr (ctrl->fd, VPPCOM_ATTR_SET_CKPAIR, &ckp_index,
1126                            &ckp_len);
1127     }
1128
1129   vtinf ("Connecting to server...");
1130   if (vcm->proto == VPPCOM_PROTO_QUIC)
1131     {
1132       quic_session->fd = vppcom_session_create (vcm->proto,
1133                                                 0 /* is_nonblocking */ );
1134       if (quic_session->fd < 0)
1135         vtfail ("vppcom_session_create()", quic_session->fd);
1136       rv = vppcom_session_connect (quic_session->fd, &vcm->server_endpt);
1137       if (rv)
1138         vtfail ("vppcom_session_connect()", rv);
1139       vtinf ("Connecting to stream...");
1140       rv = vppcom_session_stream_connect (ctrl->fd, quic_session->fd);
1141     }
1142   else
1143     rv = vppcom_session_connect (ctrl->fd, &vcm->server_endpt);
1144   if (rv)
1145     vtfail ("vppcom_session_connect()", rv);
1146   vtinf ("Control session (fd %d) connected.", ctrl->fd);
1147
1148   rv = vtc_cfg_sync (ctrl);
1149   if (rv)
1150     vtfail ("vtc_cfg_sync()", rv);
1151
1152   ctrl->cfg.ctrl_handle = ((vcl_test_cfg_t *) ctrl->rxbuf)->ctrl_handle;
1153   memset (&ctrl->stats, 0, sizeof (ctrl->stats));
1154
1155   while (ctrl->cfg.test != VCL_TEST_TYPE_EXIT)
1156     {
1157       if (vcm->dump_cfg)
1158         {
1159           vcl_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ );
1160           vcm->dump_cfg = 0;
1161         }
1162
1163       switch (ctrl->cfg.test)
1164         {
1165         case VCL_TEST_TYPE_ECHO:
1166           vtc_echo_client (vcm);
1167           break;
1168
1169         case VCL_TEST_TYPE_UNI:
1170         case VCL_TEST_TYPE_BI:
1171           vtc_stream_client (vcm);
1172           break;
1173
1174         case VCL_TEST_TYPE_EXIT:
1175           continue;
1176
1177         case VCL_TEST_TYPE_NONE:
1178         default:
1179           break;
1180         }
1181       switch (vcm->post_test)
1182         {
1183         case VCL_TEST_TYPE_EXIT:
1184           switch (ctrl->cfg.test)
1185             {
1186             case VCL_TEST_TYPE_EXIT:
1187             case VCL_TEST_TYPE_UNI:
1188             case VCL_TEST_TYPE_BI:
1189             case VCL_TEST_TYPE_ECHO:
1190               ctrl->cfg.test = VCL_TEST_TYPE_EXIT;
1191               continue;
1192
1193             case VCL_TEST_TYPE_NONE:
1194             default:
1195               break;
1196             }
1197           break;
1198
1199         case VCL_TEST_TYPE_NONE:
1200         case VCL_TEST_TYPE_ECHO:
1201         case VCL_TEST_TYPE_UNI:
1202         case VCL_TEST_TYPE_BI:
1203         default:
1204           break;
1205         }
1206
1207       memset (ctrl->txbuf, 0, ctrl->txbuf_size);
1208       memset (ctrl->rxbuf, 0, ctrl->rxbuf_size);
1209
1210       vtc_read_user_input (ctrl);
1211     }
1212
1213   vtc_ctrl_session_exit ();
1214   if (quic_session)
1215     vppcom_session_close (quic_session->fd);
1216   vppcom_app_destroy ();
1217   free (vcm->workers);
1218   return 0;
1219 }
1220
1221 /*
1222  * fd.io coding-style-patch-verification: ON
1223  *
1224  * Local Variables:
1225  * eval: (c-set-style "gnu")
1226  * End:
1227  */