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