072d767fa6ea15031311fc95a540119b29c95c40
[vpp.git] / src / plugins / hs_apps / echo_client.c
1 /*
2  * echo_client.c - vpp built-in echo client code
3  *
4  * Copyright (c) 2017-2019 by Cisco and/or its affiliates.
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at:
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #include <vnet/vnet.h>
19 #include <vlibapi/api.h>
20 #include <vlibmemory/api.h>
21 #include <hs_apps/echo_client.h>
22
23 echo_client_main_t echo_client_main;
24
25 #define ECHO_CLIENT_DBG (0)
26 #define DBG(_fmt, _args...)                     \
27     if (ECHO_CLIENT_DBG)                                \
28       clib_warning (_fmt, ##_args)
29
30 static void
31 signal_evt_to_cli_i (int *code)
32 {
33   echo_client_main_t *ecm = &echo_client_main;
34   ASSERT (vlib_get_thread_index () == 0);
35   vlib_process_signal_event (ecm->vlib_main, ecm->cli_node_index, *code, 0);
36 }
37
38 static void
39 signal_evt_to_cli (int code)
40 {
41   if (vlib_get_thread_index () != 0)
42     vl_api_rpc_call_main_thread (signal_evt_to_cli_i, (u8 *) & code,
43                                  sizeof (code));
44   else
45     signal_evt_to_cli_i (&code);
46 }
47
48 static void
49 send_data_chunk (echo_client_main_t * ecm, eclient_session_t * s)
50 {
51   u8 *test_data = ecm->connect_test_data;
52   int test_buf_len, test_buf_offset, rv;
53   u32 bytes_this_chunk;
54
55   test_buf_len = vec_len (test_data);
56   ASSERT (test_buf_len > 0);
57   test_buf_offset = s->bytes_sent % test_buf_len;
58   bytes_this_chunk = clib_min (test_buf_len - test_buf_offset,
59                                s->bytes_to_send);
60
61   if (!ecm->is_dgram)
62     {
63       if (ecm->no_copy)
64         {
65           svm_fifo_t *f = s->data.tx_fifo;
66           rv = clib_min (svm_fifo_max_enqueue_prod (f), bytes_this_chunk);
67           svm_fifo_enqueue_nocopy (f, rv);
68           session_send_io_evt_to_thread_custom (&f->master_session_index,
69                                                 s->thread_index,
70                                                 SESSION_IO_EVT_TX);
71         }
72       else
73         rv = app_send_stream (&s->data, test_data + test_buf_offset,
74                               bytes_this_chunk, 0);
75     }
76   else
77     {
78       svm_fifo_t *f = s->data.tx_fifo;
79       u32 max_enqueue = svm_fifo_max_enqueue_prod (f);
80
81       if (max_enqueue < sizeof (session_dgram_hdr_t))
82         return;
83
84       max_enqueue -= sizeof (session_dgram_hdr_t);
85
86       if (ecm->no_copy)
87         {
88           session_dgram_hdr_t hdr;
89           app_session_transport_t *at = &s->data.transport;
90
91           rv = clib_min (max_enqueue, bytes_this_chunk);
92
93           hdr.data_length = rv;
94           hdr.data_offset = 0;
95           clib_memcpy_fast (&hdr.rmt_ip, &at->rmt_ip,
96                             sizeof (ip46_address_t));
97           hdr.is_ip4 = at->is_ip4;
98           hdr.rmt_port = at->rmt_port;
99           clib_memcpy_fast (&hdr.lcl_ip, &at->lcl_ip,
100                             sizeof (ip46_address_t));
101           hdr.lcl_port = at->lcl_port;
102           svm_fifo_enqueue (f, sizeof (hdr), (u8 *) & hdr);
103           svm_fifo_enqueue_nocopy (f, rv);
104           session_send_io_evt_to_thread_custom (&f->master_session_index,
105                                                 s->thread_index,
106                                                 SESSION_IO_EVT_TX);
107         }
108       else
109         {
110           bytes_this_chunk = clib_min (bytes_this_chunk, max_enqueue);
111           rv = app_send_dgram (&s->data, test_data + test_buf_offset,
112                                bytes_this_chunk, 0);
113         }
114     }
115
116   /* If we managed to enqueue data... */
117   if (rv > 0)
118     {
119       /* Account for it... */
120       s->bytes_to_send -= rv;
121       s->bytes_sent += rv;
122
123       if (ECHO_CLIENT_DBG)
124         {
125           /* *INDENT-OFF* */
126           ELOG_TYPE_DECLARE (e) =
127             {
128               .format = "tx-enq: xfer %d bytes, sent %u remain %u",
129               .format_args = "i4i4i4",
130             };
131           /* *INDENT-ON* */
132           struct
133           {
134             u32 data[3];
135           } *ed;
136           ed = ELOG_DATA (&vlib_global_main.elog_main, e);
137           ed->data[0] = rv;
138           ed->data[1] = s->bytes_sent;
139           ed->data[2] = s->bytes_to_send;
140         }
141     }
142 }
143
144 static void
145 receive_data_chunk (echo_client_main_t * ecm, eclient_session_t * s)
146 {
147   svm_fifo_t *rx_fifo = s->data.rx_fifo;
148   u32 thread_index = vlib_get_thread_index ();
149   int n_read, i;
150
151   if (ecm->test_bytes)
152     {
153       if (!ecm->is_dgram)
154         n_read = app_recv_stream (&s->data, ecm->rx_buf[thread_index],
155                                   vec_len (ecm->rx_buf[thread_index]));
156       else
157         n_read = app_recv_dgram (&s->data, ecm->rx_buf[thread_index],
158                                  vec_len (ecm->rx_buf[thread_index]));
159     }
160   else
161     {
162       n_read = svm_fifo_max_dequeue_cons (rx_fifo);
163       svm_fifo_dequeue_drop (rx_fifo, n_read);
164     }
165
166   if (n_read > 0)
167     {
168       if (ECHO_CLIENT_DBG)
169         {
170           /* *INDENT-OFF* */
171           ELOG_TYPE_DECLARE (e) =
172             {
173               .format = "rx-deq: %d bytes",
174               .format_args = "i4",
175             };
176           /* *INDENT-ON* */
177           struct
178           {
179             u32 data[1];
180           } *ed;
181           ed = ELOG_DATA (&vlib_global_main.elog_main, e);
182           ed->data[0] = n_read;
183         }
184
185       if (ecm->test_bytes)
186         {
187           for (i = 0; i < n_read; i++)
188             {
189               if (ecm->rx_buf[thread_index][i]
190                   != ((s->bytes_received + i) & 0xff))
191                 {
192                   clib_warning ("read %d error at byte %lld, 0x%x not 0x%x",
193                                 n_read, s->bytes_received + i,
194                                 ecm->rx_buf[thread_index][i],
195                                 ((s->bytes_received + i) & 0xff));
196                   ecm->test_failed = 1;
197                 }
198             }
199         }
200       ASSERT (n_read <= s->bytes_to_receive);
201       s->bytes_to_receive -= n_read;
202       s->bytes_received += n_read;
203     }
204 }
205
206 static uword
207 echo_client_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node,
208                      vlib_frame_t * frame)
209 {
210   echo_client_main_t *ecm = &echo_client_main;
211   int my_thread_index = vlib_get_thread_index ();
212   eclient_session_t *sp;
213   int i;
214   int delete_session;
215   u32 *connection_indices;
216   u32 *connections_this_batch;
217   u32 nconnections_this_batch;
218
219   connection_indices = ecm->connection_index_by_thread[my_thread_index];
220   connections_this_batch =
221     ecm->connections_this_batch_by_thread[my_thread_index];
222
223   if ((ecm->run_test != ECHO_CLIENTS_RUNNING) ||
224       ((vec_len (connection_indices) == 0)
225        && vec_len (connections_this_batch) == 0))
226     return 0;
227
228   /* Grab another pile of connections */
229   if (PREDICT_FALSE (vec_len (connections_this_batch) == 0))
230     {
231       nconnections_this_batch =
232         clib_min (ecm->connections_per_batch, vec_len (connection_indices));
233
234       ASSERT (nconnections_this_batch > 0);
235       vec_validate (connections_this_batch, nconnections_this_batch - 1);
236       clib_memcpy_fast (connections_this_batch,
237                         connection_indices + vec_len (connection_indices)
238                         - nconnections_this_batch,
239                         nconnections_this_batch * sizeof (u32));
240       _vec_len (connection_indices) -= nconnections_this_batch;
241     }
242
243   if (PREDICT_FALSE (ecm->prev_conns != ecm->connections_per_batch
244                      && ecm->prev_conns == vec_len (connections_this_batch)))
245     {
246       ecm->repeats++;
247       ecm->prev_conns = vec_len (connections_this_batch);
248       if (ecm->repeats == 500000)
249         {
250           clib_warning ("stuck clients");
251         }
252     }
253   else
254     {
255       ecm->prev_conns = vec_len (connections_this_batch);
256       ecm->repeats = 0;
257     }
258
259   for (i = 0; i < vec_len (connections_this_batch); i++)
260     {
261       delete_session = 1;
262
263       sp = pool_elt_at_index (ecm->sessions, connections_this_batch[i]);
264
265       if (sp->bytes_to_send > 0)
266         {
267           send_data_chunk (ecm, sp);
268           delete_session = 0;
269         }
270       if (sp->bytes_to_receive > 0)
271         {
272           delete_session = 0;
273         }
274       if (PREDICT_FALSE (delete_session == 1))
275         {
276           session_t *s;
277
278           clib_atomic_fetch_add (&ecm->tx_total, sp->bytes_sent);
279           clib_atomic_fetch_add (&ecm->rx_total, sp->bytes_received);
280           s = session_get_from_handle_if_valid (sp->vpp_session_handle);
281
282           if (s)
283             {
284               vnet_disconnect_args_t _a, *a = &_a;
285               a->handle = session_handle (s);
286               a->app_index = ecm->app_index;
287               vnet_disconnect_session (a);
288
289               vec_delete (connections_this_batch, 1, i);
290               i--;
291               clib_atomic_fetch_add (&ecm->ready_connections, -1);
292             }
293           else
294             {
295               clib_warning ("session AWOL?");
296               vec_delete (connections_this_batch, 1, i);
297             }
298
299           /* Kick the debug CLI process */
300           if (ecm->ready_connections == 0)
301             {
302               signal_evt_to_cli (2);
303             }
304         }
305     }
306
307   ecm->connection_index_by_thread[my_thread_index] = connection_indices;
308   ecm->connections_this_batch_by_thread[my_thread_index] =
309     connections_this_batch;
310   return 0;
311 }
312
313 /* *INDENT-OFF* */
314 VLIB_REGISTER_NODE (echo_clients_node) =
315 {
316   .function = echo_client_node_fn,
317   .name = "echo-clients",
318   .type = VLIB_NODE_TYPE_INPUT,
319   .state = VLIB_NODE_STATE_DISABLED,
320 };
321 /* *INDENT-ON* */
322
323 static int
324 echo_clients_init (vlib_main_t * vm)
325 {
326   echo_client_main_t *ecm = &echo_client_main;
327   vlib_thread_main_t *vtm = vlib_get_thread_main ();
328   u32 num_threads;
329   int i;
330
331   num_threads = 1 /* main thread */  + vtm->n_threads;
332
333   /* Init test data. Big buffer */
334   vec_validate (ecm->connect_test_data, 4 * 1024 * 1024 - 1);
335   for (i = 0; i < vec_len (ecm->connect_test_data); i++)
336     ecm->connect_test_data[i] = i & 0xff;
337
338   vec_validate (ecm->rx_buf, num_threads - 1);
339   for (i = 0; i < num_threads; i++)
340     vec_validate (ecm->rx_buf[i], vec_len (ecm->connect_test_data) - 1);
341
342   ecm->is_init = 1;
343
344   vec_validate (ecm->connection_index_by_thread, vtm->n_vlib_mains);
345   vec_validate (ecm->connections_this_batch_by_thread, vtm->n_vlib_mains);
346   vec_validate (ecm->quic_session_index_by_thread, vtm->n_vlib_mains);
347   vec_validate (ecm->vpp_event_queue, vtm->n_vlib_mains);
348
349   return 0;
350 }
351
352 static int
353 quic_echo_clients_qsession_connected_callback (u32 app_index, u32 api_context,
354                                                session_t * s,
355                                                session_error_t err)
356 {
357   echo_client_main_t *ecm = &echo_client_main;
358   vnet_connect_args_t *a = 0;
359   int rv;
360   u8 thread_index = vlib_get_thread_index ();
361   session_endpoint_cfg_t sep = SESSION_ENDPOINT_CFG_NULL;
362   u32 stream_n;
363   session_handle_t handle;
364
365   DBG ("QUIC Connection handle %d", session_handle (s));
366
367   vec_validate (a, 1);
368   a->uri = (char *) ecm->connect_uri;
369   if (parse_uri (a->uri, &sep))
370     return -1;
371   sep.parent_handle = handle = session_handle (s);
372
373   for (stream_n = 0; stream_n < ecm->quic_streams; stream_n++)
374     {
375       clib_memset (a, 0, sizeof (*a));
376       a->app_index = ecm->app_index;
377       a->api_context = -1 - api_context;
378       clib_memcpy (&a->sep_ext, &sep, sizeof (sep));
379
380       DBG ("QUIC opening stream %d", stream_n);
381       if ((rv = vnet_connect (a)))
382         {
383           clib_error ("Stream session %d opening failed: %d", stream_n, rv);
384           return -1;
385         }
386       DBG ("QUIC stream %d connected", stream_n);
387     }
388   /*
389    * 's' is no longer valid, its underlying pool could have been moved in
390    * vnet_connect()
391    */
392   vec_add1 (ecm->quic_session_index_by_thread[thread_index], handle);
393   vec_free (a);
394   return 0;
395 }
396
397 static int
398 quic_echo_clients_session_connected_callback (u32 app_index, u32 api_context,
399                                               session_t * s,
400                                               session_error_t err)
401 {
402   echo_client_main_t *ecm = &echo_client_main;
403   eclient_session_t *session;
404   u32 session_index;
405   u8 thread_index;
406
407   if (PREDICT_FALSE (ecm->run_test != ECHO_CLIENTS_STARTING))
408     return -1;
409
410   if (err)
411     {
412       clib_warning ("connection %d failed!", api_context);
413       ecm->run_test = ECHO_CLIENTS_EXITING;
414       signal_evt_to_cli (-1);
415       return 0;
416     }
417
418   if (s->listener_handle == SESSION_INVALID_HANDLE)
419     return quic_echo_clients_qsession_connected_callback (app_index,
420                                                           api_context, s,
421                                                           err);
422   DBG ("STREAM Connection callback %d", api_context);
423
424   thread_index = s->thread_index;
425   ASSERT (thread_index == vlib_get_thread_index ()
426           || session_transport_service_type (s) == TRANSPORT_SERVICE_CL);
427
428   if (!ecm->vpp_event_queue[thread_index])
429     ecm->vpp_event_queue[thread_index] =
430       session_main_get_vpp_event_queue (thread_index);
431
432   /*
433    * Setup session
434    */
435   clib_spinlock_lock_if_init (&ecm->sessions_lock);
436   pool_get (ecm->sessions, session);
437   clib_spinlock_unlock_if_init (&ecm->sessions_lock);
438
439   clib_memset (session, 0, sizeof (*session));
440   session_index = session - ecm->sessions;
441   session->bytes_to_send = ecm->bytes_to_send;
442   session->bytes_to_receive = ecm->no_return ? 0ULL : ecm->bytes_to_send;
443   session->data.rx_fifo = s->rx_fifo;
444   session->data.rx_fifo->client_session_index = session_index;
445   session->data.tx_fifo = s->tx_fifo;
446   session->data.tx_fifo->client_session_index = session_index;
447   session->data.vpp_evt_q = ecm->vpp_event_queue[thread_index];
448   session->vpp_session_handle = session_handle (s);
449
450   if (ecm->is_dgram)
451     {
452       transport_connection_t *tc;
453       tc = session_get_transport (s);
454       clib_memcpy_fast (&session->data.transport, tc,
455                         sizeof (session->data.transport));
456       session->data.is_dgram = 1;
457     }
458
459   vec_add1 (ecm->connection_index_by_thread[thread_index], session_index);
460   clib_atomic_fetch_add (&ecm->ready_connections, 1);
461   if (ecm->ready_connections == ecm->expected_connections)
462     {
463       ecm->run_test = ECHO_CLIENTS_RUNNING;
464       /* Signal the CLI process that the action is starting... */
465       signal_evt_to_cli (1);
466     }
467
468   return 0;
469 }
470
471 static int
472 echo_clients_session_connected_callback (u32 app_index, u32 api_context,
473                                          session_t * s, session_error_t err)
474 {
475   echo_client_main_t *ecm = &echo_client_main;
476   eclient_session_t *session;
477   u32 session_index;
478   u8 thread_index;
479
480   if (PREDICT_FALSE (ecm->run_test != ECHO_CLIENTS_STARTING))
481     return -1;
482
483   if (err)
484     {
485       clib_warning ("connection %d failed!", api_context);
486       ecm->run_test = ECHO_CLIENTS_EXITING;
487       signal_evt_to_cli (-1);
488       return 0;
489     }
490
491   thread_index = s->thread_index;
492   ASSERT (thread_index == vlib_get_thread_index ()
493           || session_transport_service_type (s) == TRANSPORT_SERVICE_CL);
494
495   if (!ecm->vpp_event_queue[thread_index])
496     ecm->vpp_event_queue[thread_index] =
497       session_main_get_vpp_event_queue (thread_index);
498
499   /*
500    * Setup session
501    */
502   clib_spinlock_lock_if_init (&ecm->sessions_lock);
503   pool_get (ecm->sessions, session);
504   clib_spinlock_unlock_if_init (&ecm->sessions_lock);
505
506   clib_memset (session, 0, sizeof (*session));
507   session_index = session - ecm->sessions;
508   session->bytes_to_send = ecm->bytes_to_send;
509   session->bytes_to_receive = ecm->no_return ? 0ULL : ecm->bytes_to_send;
510   session->data.rx_fifo = s->rx_fifo;
511   session->data.rx_fifo->client_session_index = session_index;
512   session->data.tx_fifo = s->tx_fifo;
513   session->data.tx_fifo->client_session_index = session_index;
514   session->data.vpp_evt_q = ecm->vpp_event_queue[thread_index];
515   session->vpp_session_handle = session_handle (s);
516
517   if (ecm->is_dgram)
518     {
519       transport_connection_t *tc;
520       tc = session_get_transport (s);
521       clib_memcpy_fast (&session->data.transport, tc,
522                         sizeof (session->data.transport));
523       session->data.is_dgram = 1;
524     }
525
526   vec_add1 (ecm->connection_index_by_thread[thread_index], session_index);
527   clib_atomic_fetch_add (&ecm->ready_connections, 1);
528   if (ecm->ready_connections == ecm->expected_connections)
529     {
530       ecm->run_test = ECHO_CLIENTS_RUNNING;
531       /* Signal the CLI process that the action is starting... */
532       signal_evt_to_cli (1);
533     }
534
535   return 0;
536 }
537
538 static void
539 echo_clients_session_reset_callback (session_t * s)
540 {
541   echo_client_main_t *ecm = &echo_client_main;
542   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
543
544   if (s->session_state == SESSION_STATE_READY)
545     clib_warning ("Reset active connection %U", format_session, s, 2);
546
547   a->handle = session_handle (s);
548   a->app_index = ecm->app_index;
549   vnet_disconnect_session (a);
550   return;
551 }
552
553 static int
554 echo_clients_session_create_callback (session_t * s)
555 {
556   return 0;
557 }
558
559 static void
560 echo_clients_session_disconnect_callback (session_t * s)
561 {
562   echo_client_main_t *ecm = &echo_client_main;
563   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
564   a->handle = session_handle (s);
565   a->app_index = ecm->app_index;
566   vnet_disconnect_session (a);
567   return;
568 }
569
570 void
571 echo_clients_session_disconnect (session_t * s)
572 {
573   echo_client_main_t *ecm = &echo_client_main;
574   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
575   a->handle = session_handle (s);
576   a->app_index = ecm->app_index;
577   vnet_disconnect_session (a);
578 }
579
580 static int
581 echo_clients_rx_callback (session_t * s)
582 {
583   echo_client_main_t *ecm = &echo_client_main;
584   eclient_session_t *sp;
585
586   if (PREDICT_FALSE (ecm->run_test != ECHO_CLIENTS_RUNNING))
587     {
588       echo_clients_session_disconnect (s);
589       return -1;
590     }
591
592   sp = pool_elt_at_index (ecm->sessions, s->rx_fifo->client_session_index);
593   receive_data_chunk (ecm, sp);
594
595   if (svm_fifo_max_dequeue_cons (s->rx_fifo))
596     {
597       if (svm_fifo_set_event (s->rx_fifo))
598         session_send_io_evt_to_thread (s->rx_fifo, SESSION_IO_EVT_BUILTIN_RX);
599     }
600   return 0;
601 }
602
603 int
604 echo_client_add_segment_callback (u32 client_index, u64 segment_handle)
605 {
606   /* New heaps may be added */
607   return 0;
608 }
609
610 /* *INDENT-OFF* */
611 static session_cb_vft_t echo_clients = {
612   .session_reset_callback = echo_clients_session_reset_callback,
613   .session_connected_callback = echo_clients_session_connected_callback,
614   .session_accept_callback = echo_clients_session_create_callback,
615   .session_disconnect_callback = echo_clients_session_disconnect_callback,
616   .builtin_app_rx_callback = echo_clients_rx_callback,
617   .add_segment_callback = echo_client_add_segment_callback
618 };
619 /* *INDENT-ON* */
620
621 static clib_error_t *
622 echo_clients_attach (u8 * appns_id, u64 appns_flags, u64 appns_secret)
623 {
624   vnet_app_add_tls_cert_args_t _a_cert, *a_cert = &_a_cert;
625   vnet_app_add_tls_key_args_t _a_key, *a_key = &_a_key;
626   u32 prealloc_fifos, segment_size = 256 << 20;
627   echo_client_main_t *ecm = &echo_client_main;
628   vnet_app_attach_args_t _a, *a = &_a;
629   u64 options[18];
630   int rv;
631
632   clib_memset (a, 0, sizeof (*a));
633   clib_memset (options, 0, sizeof (options));
634
635   a->api_client_index = ~0;
636   a->name = format (0, "echo_client");
637   if (ecm->transport_proto == TRANSPORT_PROTO_QUIC)
638     echo_clients.session_connected_callback =
639       quic_echo_clients_session_connected_callback;
640   a->session_cb_vft = &echo_clients;
641
642   prealloc_fifos = ecm->prealloc_fifos ? ecm->expected_connections : 1;
643
644   if (ecm->private_segment_size)
645     segment_size = ecm->private_segment_size;
646
647   options[APP_OPTIONS_ACCEPT_COOKIE] = 0x12345678;
648   options[APP_OPTIONS_SEGMENT_SIZE] = segment_size;
649   options[APP_OPTIONS_ADD_SEGMENT_SIZE] = segment_size;
650   options[APP_OPTIONS_RX_FIFO_SIZE] = ecm->fifo_size;
651   options[APP_OPTIONS_TX_FIFO_SIZE] = ecm->fifo_size;
652   options[APP_OPTIONS_PRIVATE_SEGMENT_COUNT] = ecm->private_segment_count;
653   options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = prealloc_fifos;
654   options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
655   options[APP_OPTIONS_TLS_ENGINE] = ecm->tls_engine;
656   options[APP_OPTIONS_PCT_FIRST_ALLOC] = 100;
657   if (appns_id)
658     {
659       options[APP_OPTIONS_FLAGS] |= appns_flags;
660       options[APP_OPTIONS_NAMESPACE_SECRET] = appns_secret;
661     }
662   a->options = options;
663   a->namespace_id = appns_id;
664
665   if ((rv = vnet_application_attach (a)))
666     return clib_error_return (0, "attach returned %d", rv);
667
668   ecm->app_index = a->app_index;
669   vec_free (a->name);
670
671   clib_memset (a_cert, 0, sizeof (*a_cert));
672   a_cert->app_index = a->app_index;
673   vec_validate (a_cert->cert, test_srv_crt_rsa_len);
674   clib_memcpy_fast (a_cert->cert, test_srv_crt_rsa, test_srv_crt_rsa_len);
675   vnet_app_add_tls_cert (a_cert);
676
677   clib_memset (a_key, 0, sizeof (*a_key));
678   a_key->app_index = a->app_index;
679   vec_validate (a_key->key, test_srv_key_rsa_len);
680   clib_memcpy_fast (a_key->key, test_srv_key_rsa, test_srv_key_rsa_len);
681   vnet_app_add_tls_key (a_key);
682   return 0;
683 }
684
685 static int
686 echo_clients_detach ()
687 {
688   echo_client_main_t *ecm = &echo_client_main;
689   vnet_app_detach_args_t _da, *da = &_da;
690   int rv;
691
692   da->app_index = ecm->app_index;
693   da->api_client_index = ~0;
694   rv = vnet_application_detach (da);
695   ecm->test_client_attached = 0;
696   ecm->app_index = ~0;
697   return rv;
698 }
699
700 static void *
701 echo_client_thread_fn (void *arg)
702 {
703   return 0;
704 }
705
706 /** Start a transmit thread */
707 int
708 echo_clients_start_tx_pthread (echo_client_main_t * ecm)
709 {
710   if (ecm->client_thread_handle == 0)
711     {
712       int rv = pthread_create (&ecm->client_thread_handle,
713                                NULL /*attr */ ,
714                                echo_client_thread_fn, 0);
715       if (rv)
716         {
717           ecm->client_thread_handle = 0;
718           return -1;
719         }
720     }
721   return 0;
722 }
723
724 clib_error_t *
725 echo_clients_connect (vlib_main_t * vm, u32 n_clients)
726 {
727   echo_client_main_t *ecm = &echo_client_main;
728   vnet_connect_args_t _a, *a = &_a;
729   int i, rv;
730
731   clib_memset (a, 0, sizeof (*a));
732
733   for (i = 0; i < n_clients; i++)
734     {
735       a->uri = (char *) ecm->connect_uri;
736       a->api_context = i;
737       a->app_index = ecm->app_index;
738
739       vlib_worker_thread_barrier_sync (vm);
740       if ((rv = vnet_connect_uri (a)))
741         {
742           vlib_worker_thread_barrier_release (vm);
743           return clib_error_return (0, "connect returned: %d", rv);
744         }
745       vlib_worker_thread_barrier_release (vm);
746
747       /* Crude pacing for call setups  */
748       if ((i % 16) == 0)
749         vlib_process_suspend (vm, 100e-6);
750       ASSERT (i + 1 >= ecm->ready_connections);
751       while (i + 1 - ecm->ready_connections > 128)
752         vlib_process_suspend (vm, 1e-3);
753     }
754   return 0;
755 }
756
757 #define ec_cli_output(_fmt, _args...)                   \
758   if (!ecm->no_output)                                  \
759     vlib_cli_output(vm, _fmt, ##_args)
760
761 static clib_error_t *
762 echo_clients_command_fn (vlib_main_t * vm,
763                          unformat_input_t * input, vlib_cli_command_t * cmd)
764 {
765   echo_client_main_t *ecm = &echo_client_main;
766   vlib_thread_main_t *thread_main = vlib_get_thread_main ();
767   u64 tmp, total_bytes, appns_flags = 0, appns_secret = 0;
768   f64 test_timeout = 20.0, syn_timeout = 20.0, delta;
769   char *default_uri = "tcp://6.0.1.1/1234";
770   uword *event_data = 0, event_type;
771   f64 time_before_connects;
772   u32 n_clients = 1;
773   int preallocate_sessions = 0;
774   char *transfer_type;
775   clib_error_t *error = 0;
776   u8 *appns_id = 0;
777   int i;
778   session_endpoint_cfg_t sep = SESSION_ENDPOINT_CFG_NULL;
779   int rv;
780
781   ecm->quic_streams = 1;
782   ecm->bytes_to_send = 8192;
783   ecm->no_return = 0;
784   ecm->fifo_size = 64 << 10;
785   ecm->connections_per_batch = 1000;
786   ecm->private_segment_count = 0;
787   ecm->private_segment_size = 0;
788   ecm->no_output = 0;
789   ecm->test_bytes = 0;
790   ecm->test_failed = 0;
791   ecm->vlib_main = vm;
792   ecm->tls_engine = CRYPTO_ENGINE_OPENSSL;
793   ecm->no_copy = 0;
794   ecm->run_test = ECHO_CLIENTS_STARTING;
795
796   if (thread_main->n_vlib_mains > 1)
797     clib_spinlock_init (&ecm->sessions_lock);
798   vec_free (ecm->connect_uri);
799
800   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
801     {
802       if (unformat (input, "uri %s", &ecm->connect_uri))
803         ;
804       else if (unformat (input, "nclients %d", &n_clients))
805         ;
806       else if (unformat (input, "quic-streams %d", &ecm->quic_streams))
807         ;
808       else if (unformat (input, "mbytes %lld", &tmp))
809         ecm->bytes_to_send = tmp << 20;
810       else if (unformat (input, "gbytes %lld", &tmp))
811         ecm->bytes_to_send = tmp << 30;
812       else if (unformat (input, "bytes %lld", &ecm->bytes_to_send))
813         ;
814       else if (unformat (input, "test-timeout %f", &test_timeout))
815         ;
816       else if (unformat (input, "syn-timeout %f", &syn_timeout))
817         ;
818       else if (unformat (input, "no-return"))
819         ecm->no_return = 1;
820       else if (unformat (input, "fifo-size %d", &ecm->fifo_size))
821         ecm->fifo_size <<= 10;
822       else if (unformat (input, "private-segment-count %d",
823                          &ecm->private_segment_count))
824         ;
825       else if (unformat (input, "private-segment-size %U",
826                          unformat_memory_size, &tmp))
827         {
828           if (tmp >= 0x100000000ULL)
829             return clib_error_return
830               (0, "private segment size %lld (%llu) too large", tmp, tmp);
831           ecm->private_segment_size = tmp;
832         }
833       else if (unformat (input, "preallocate-fifos"))
834         ecm->prealloc_fifos = 1;
835       else if (unformat (input, "preallocate-sessions"))
836         preallocate_sessions = 1;
837       else
838         if (unformat (input, "client-batch %d", &ecm->connections_per_batch))
839         ;
840       else if (unformat (input, "appns %_%v%_", &appns_id))
841         ;
842       else if (unformat (input, "all-scope"))
843         appns_flags |= (APP_OPTIONS_FLAGS_USE_GLOBAL_SCOPE
844                         | APP_OPTIONS_FLAGS_USE_LOCAL_SCOPE);
845       else if (unformat (input, "local-scope"))
846         appns_flags = APP_OPTIONS_FLAGS_USE_LOCAL_SCOPE;
847       else if (unformat (input, "global-scope"))
848         appns_flags = APP_OPTIONS_FLAGS_USE_GLOBAL_SCOPE;
849       else if (unformat (input, "secret %lu", &appns_secret))
850         ;
851       else if (unformat (input, "no-output"))
852         ecm->no_output = 1;
853       else if (unformat (input, "test-bytes"))
854         ecm->test_bytes = 1;
855       else if (unformat (input, "tls-engine %d", &ecm->tls_engine))
856         ;
857       else
858         return clib_error_return (0, "failed: unknown input `%U'",
859                                   format_unformat_error, input);
860     }
861
862   /* Store cli process node index for signalling */
863   ecm->cli_node_index =
864     vlib_get_current_process (vm)->node_runtime.node_index;
865
866   if (ecm->is_init == 0)
867     {
868       if (echo_clients_init (vm))
869         return clib_error_return (0, "failed init");
870     }
871
872
873   ecm->ready_connections = 0;
874   ecm->expected_connections = n_clients * ecm->quic_streams;
875   ecm->rx_total = 0;
876   ecm->tx_total = 0;
877
878   if (!ecm->connect_uri)
879     {
880       clib_warning ("No uri provided. Using default: %s", default_uri);
881       ecm->connect_uri = format (0, "%s%c", default_uri, 0);
882     }
883
884   if ((rv = parse_uri ((char *) ecm->connect_uri, &sep)))
885     return clib_error_return (0, "Uri parse error: %d", rv);
886   ecm->transport_proto = sep.transport_proto;
887   ecm->is_dgram = (sep.transport_proto == TRANSPORT_PROTO_UDP);
888
889 #if ECHO_CLIENT_PTHREAD
890   echo_clients_start_tx_pthread ();
891 #endif
892
893   vlib_worker_thread_barrier_sync (vm);
894   vnet_session_enable_disable (vm, 1 /* turn on session and transports */ );
895   vlib_worker_thread_barrier_release (vm);
896
897   if (ecm->test_client_attached == 0)
898     {
899       if ((error = echo_clients_attach (appns_id, appns_flags, appns_secret)))
900         {
901           vec_free (appns_id);
902           clib_error_report (error);
903           return error;
904         }
905       vec_free (appns_id);
906     }
907   ecm->test_client_attached = 1;
908
909   /* Turn on the builtin client input nodes */
910   for (i = 0; i < thread_main->n_vlib_mains; i++)
911     vlib_node_set_state (vlib_mains[i], echo_clients_node.index,
912                          VLIB_NODE_STATE_POLLING);
913
914   if (preallocate_sessions)
915     pool_init_fixed (ecm->sessions, 1.1 * n_clients);
916
917   /* Fire off connect requests */
918   time_before_connects = vlib_time_now (vm);
919   if ((error = echo_clients_connect (vm, n_clients)))
920     {
921       goto cleanup;
922     }
923
924   /* Park until the sessions come up, or ten seconds elapse... */
925   vlib_process_wait_for_event_or_clock (vm, syn_timeout);
926   event_type = vlib_process_get_events (vm, &event_data);
927   switch (event_type)
928     {
929     case ~0:
930       ec_cli_output ("Timeout with only %d sessions active...",
931                      ecm->ready_connections);
932       error = clib_error_return (0, "failed: syn timeout with %d sessions",
933                                  ecm->ready_connections);
934       goto cleanup;
935
936     case 1:
937       delta = vlib_time_now (vm) - time_before_connects;
938       if (delta != 0.0)
939         ec_cli_output ("%d three-way handshakes in %.2f seconds %.2f/s",
940                        n_clients, delta, ((f64) n_clients) / delta);
941
942       ecm->test_start_time = vlib_time_now (ecm->vlib_main);
943       ec_cli_output ("Test started at %.6f", ecm->test_start_time);
944       break;
945
946     default:
947       ec_cli_output ("unexpected event(1): %d", event_type);
948       error = clib_error_return (0, "failed: unexpected event(1): %d",
949                                  event_type);
950       goto cleanup;
951     }
952
953   /* Now wait for the sessions to finish... */
954   vlib_process_wait_for_event_or_clock (vm, test_timeout);
955   event_type = vlib_process_get_events (vm, &event_data);
956   switch (event_type)
957     {
958     case ~0:
959       ec_cli_output ("Timeout with %d sessions still active...",
960                      ecm->ready_connections);
961       error = clib_error_return (0, "failed: timeout with %d sessions",
962                                  ecm->ready_connections);
963       goto cleanup;
964
965     case 2:
966       ecm->test_end_time = vlib_time_now (vm);
967       ec_cli_output ("Test finished at %.6f", ecm->test_end_time);
968       break;
969
970     default:
971       ec_cli_output ("unexpected event(2): %d", event_type);
972       error = clib_error_return (0, "failed: unexpected event(2): %d",
973                                  event_type);
974       goto cleanup;
975     }
976
977   delta = ecm->test_end_time - ecm->test_start_time;
978   if (delta != 0.0)
979     {
980       total_bytes = (ecm->no_return ? ecm->tx_total : ecm->rx_total);
981       transfer_type = ecm->no_return ? "half-duplex" : "full-duplex";
982       ec_cli_output ("%lld bytes (%lld mbytes, %lld gbytes) in %.2f seconds",
983                      total_bytes, total_bytes / (1ULL << 20),
984                      total_bytes / (1ULL << 30), delta);
985       ec_cli_output ("%.2f bytes/second %s", ((f64) total_bytes) / (delta),
986                      transfer_type);
987       ec_cli_output ("%.4f gbit/second %s",
988                      (((f64) total_bytes * 8.0) / delta / 1e9),
989                      transfer_type);
990     }
991   else
992     {
993       ec_cli_output ("zero delta-t?");
994       error = clib_error_return (0, "failed: zero delta-t");
995       goto cleanup;
996     }
997
998   if (ecm->test_bytes && ecm->test_failed)
999     error = clib_error_return (0, "failed: test bytes");
1000
1001 cleanup:
1002   ecm->run_test = ECHO_CLIENTS_EXITING;
1003   vlib_process_wait_for_event_or_clock (vm, 10e-3);
1004   for (i = 0; i < vec_len (ecm->connection_index_by_thread); i++)
1005     {
1006       vec_reset_length (ecm->connection_index_by_thread[i]);
1007       vec_reset_length (ecm->connections_this_batch_by_thread[i]);
1008       vec_reset_length (ecm->quic_session_index_by_thread[i]);
1009     }
1010
1011   pool_free (ecm->sessions);
1012
1013   /* Detach the application, so we can use different fifo sizes next time */
1014   if (ecm->test_client_attached)
1015     {
1016       if (echo_clients_detach ())
1017         {
1018           error = clib_error_return (0, "failed: app detach");
1019           ec_cli_output ("WARNING: app detach failed...");
1020         }
1021     }
1022   if (error)
1023     ec_cli_output ("test failed");
1024   vec_free (ecm->connect_uri);
1025   return error;
1026 }
1027
1028 /* *INDENT-OFF* */
1029 VLIB_CLI_COMMAND (echo_clients_command, static) =
1030 {
1031   .path = "test echo clients",
1032   .short_help = "test echo clients [nclients %d][[m|g]bytes <bytes>]"
1033       "[test-timeout <time>][syn-timeout <time>][no-return][fifo-size <size>]"
1034       "[private-segment-count <count>][private-segment-size <bytes>[m|g]]"
1035       "[preallocate-fifos][preallocate-sessions][client-batch <batch-size>]"
1036       "[uri <tcp://ip/port>][test-bytes][no-output]",
1037   .function = echo_clients_command_fn,
1038   .is_mp_safe = 1,
1039 };
1040 /* *INDENT-ON* */
1041
1042 clib_error_t *
1043 echo_clients_main_init (vlib_main_t * vm)
1044 {
1045   echo_client_main_t *ecm = &echo_client_main;
1046   ecm->is_init = 0;
1047   return 0;
1048 }
1049
1050 VLIB_INIT_FUNCTION (echo_clients_main_init);
1051
1052 /*
1053  * fd.io coding-style-patch-verification: ON
1054  *
1055  * Local Variables:
1056  * eval: (c-set-style "gnu")
1057  * End:
1058  */