4407427e652e3833553ed4eb215a1636ec8d5b7a
[vpp.git] / src / plugins / hs_apps / http_tps.c
1 /*
2  * Copyright (c) 2022 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 <vnet/session/application.h>
17 #include <vnet/session/application_interface.h>
18 #include <vnet/session/session.h>
19 #include <http/http.h>
20
21 typedef struct
22 {
23   CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
24   u32 session_index;
25   u32 thread_index;
26   u64 data_len;
27   u64 data_offset;
28   u32 vpp_session_index;
29   u8 *uri;
30 } hts_session_t;
31
32 typedef struct hs_main_
33 {
34   hts_session_t **sessions;
35   u32 app_index;
36
37   u32 ckpair_index;
38   u8 *test_data;
39
40   /** Hash table of listener uris to handles */
41   uword *uri_to_handle;
42
43   /*
44    * Configs
45    */
46   u8 *uri;
47   u32 fifo_size;
48   u64 segment_size;
49   u8 debug_level;
50   u8 no_zc;
51   u8 *default_uri;
52 } hts_main_t;
53
54 static hts_main_t hts_main;
55
56 static hts_session_t *
57 hts_session_alloc (u32 thread_index)
58 {
59   hts_main_t *htm = &hts_main;
60   hts_session_t *hs;
61
62   pool_get_zero (htm->sessions[thread_index], hs);
63   hs->session_index = hs - htm->sessions[thread_index];
64   hs->thread_index = thread_index;
65
66   return hs;
67 }
68
69 static hts_session_t *
70 hts_session_get (u32 thread_index, u32 hts_index)
71 {
72   hts_main_t *htm = &hts_main;
73
74   if (pool_is_free_index (htm->sessions[thread_index], hts_index))
75     return 0;
76
77   return pool_elt_at_index (htm->sessions[thread_index], hts_index);
78 }
79
80 static void
81 hts_session_free (hts_session_t *hs)
82 {
83   hts_main_t *htm = &hts_main;
84   u32 thread = hs->thread_index;
85
86   if (CLIB_DEBUG)
87     clib_memset (hs, 0xfa, sizeof (*hs));
88
89   pool_put (htm->sessions[thread], hs);
90 }
91
92 static void
93 hts_session_tx_zc (hts_session_t *hs, session_t *ts)
94 {
95   u32 to_send, space;
96   u64 max_send;
97   int rv;
98
99   rv = svm_fifo_fill_chunk_list (ts->tx_fifo);
100   if (rv < 0)
101     {
102       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
103       return;
104     }
105
106   max_send = hs->data_len - hs->data_offset;
107   space = svm_fifo_max_enqueue (ts->tx_fifo);
108   ASSERT (space != 0);
109   to_send = clib_min (space, max_send);
110
111   svm_fifo_enqueue_nocopy (ts->tx_fifo, to_send);
112
113   hs->data_offset += to_send;
114
115   if (to_send < max_send)
116     svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
117
118   if (svm_fifo_set_event (ts->tx_fifo))
119     session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
120 }
121
122 static void
123 hts_session_tx_no_zc (hts_session_t *hs, session_t *ts)
124 {
125   u32 n_segs, buf_offset, buf_left;
126   u64 max_send = 32 << 10, left;
127   hts_main_t *htm = &hts_main;
128   svm_fifo_seg_t seg[2];
129   int sent;
130
131   left = hs->data_len - hs->data_offset;
132   max_send = clib_min (left, max_send);
133   buf_offset = hs->data_offset % vec_len (htm->test_data);
134   buf_left = vec_len (htm->test_data) - buf_offset;
135
136   if (buf_left < max_send)
137     {
138       seg[0].data = htm->test_data + buf_offset;
139       seg[0].len = buf_left;
140       seg[1].data = htm->test_data;
141       seg[1].len = max_send - buf_left;
142       n_segs = 2;
143     }
144   else
145     {
146       seg[0].data = htm->test_data + buf_offset;
147       seg[0].len = max_send;
148       n_segs = 1;
149     }
150
151   sent = svm_fifo_enqueue_segments (ts->tx_fifo, seg, n_segs,
152                                     1 /* allow partial */);
153
154   if (sent <= 0)
155     {
156       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
157       return;
158     }
159
160   hs->data_offset += sent;
161
162   if (sent < left)
163     svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
164
165   if (svm_fifo_set_event (ts->tx_fifo))
166     session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
167 }
168
169 static inline void
170 hts_session_tx (hts_session_t *hs, session_t *ts)
171 {
172   hts_main_t *htm = &hts_main;
173
174   if (!htm->no_zc)
175     hts_session_tx_zc (hs, ts);
176   else
177     hts_session_tx_no_zc (hs, ts);
178 }
179
180 static void
181 hts_start_send_data (hts_session_t *hs, http_status_code_t status)
182 {
183   http_msg_t msg;
184   session_t *ts;
185   int rv;
186
187   msg.type = HTTP_MSG_REPLY;
188   msg.code = status;
189   msg.content_type = HTTP_CONTENT_TEXT_HTML;
190   msg.data.type = HTTP_MSG_DATA_INLINE;
191   msg.data.len = hs->data_len;
192
193   ts = session_get (hs->vpp_session_index, hs->thread_index);
194   rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
195   ASSERT (rv == sizeof (msg));
196
197   if (!msg.data.len)
198     {
199       if (svm_fifo_set_event (ts->tx_fifo))
200         session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
201       return;
202     }
203
204   hts_session_tx (hs, ts);
205 }
206
207 static int
208 try_test_file (hts_session_t *hs, u8 *request)
209 {
210   char *test_str = "test_file";
211   hts_main_t *htm = &hts_main;
212   unformat_input_t input;
213   uword file_size;
214   int rc = 0;
215
216   if (memcmp (request, test_str, clib_strnlen (test_str, 9)))
217     return -1;
218
219   unformat_init_vector (&input, vec_dup (request));
220   if (!unformat (&input, "test_file_%U", unformat_memory_size, &file_size))
221     {
222       rc = -1;
223       goto done;
224     }
225
226   if (unformat_check_input (&input) != UNFORMAT_END_OF_INPUT)
227     {
228       rc = -1;
229       goto done;
230     }
231
232   if (htm->debug_level)
233     clib_warning ("Requested file size %U", format_memory_size, file_size);
234
235   hs->data_len = file_size;
236
237   hts_start_send_data (hs, HTTP_STATUS_OK);
238
239 done:
240   unformat_free (&input);
241
242   return rc;
243 }
244
245 static int
246 hts_ts_rx_callback (session_t *ts)
247 {
248   hts_session_t *hs;
249   u8 *request = 0;
250   http_msg_t msg;
251   int rv;
252
253   hs = hts_session_get (ts->thread_index, ts->opaque);
254
255   /* Read the http message header */
256   rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
257   ASSERT (rv == sizeof (msg));
258
259   if (msg.type != HTTP_MSG_REQUEST || msg.method_type != HTTP_REQ_GET)
260     {
261       hts_start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED);
262       goto done;
263     }
264
265   if (!msg.data.len)
266     {
267       hts_start_send_data (hs, HTTP_STATUS_BAD_REQUEST);
268       goto done;
269     }
270
271   vec_validate (request, msg.data.len - 1);
272   rv = svm_fifo_dequeue (ts->rx_fifo, msg.data.len, request);
273
274   if (try_test_file (hs, request))
275     hts_start_send_data (hs, HTTP_STATUS_NOT_FOUND);
276
277 done:
278
279   return 0;
280 }
281
282 static int
283 hs_ts_tx_callback (session_t *ts)
284 {
285   hts_session_t *hs;
286
287   hs = hts_session_get (ts->thread_index, ts->opaque);
288   if (!hs)
289     return 0;
290
291   hts_session_tx (hs, ts);
292
293   return 0;
294 }
295
296 static int
297 hts_ts_accept_callback (session_t *ts)
298 {
299   hts_session_t *hs;
300
301   hs = hts_session_alloc (ts->thread_index);
302   hs->vpp_session_index = ts->session_index;
303
304   ts->opaque = hs->session_index;
305   ts->session_state = SESSION_STATE_READY;
306
307   return 0;
308 }
309
310 static int
311 hts_ts_connected_callback (u32 app_index, u32 api_context, session_t *s,
312                            session_error_t err)
313 {
314   clib_warning ("called...");
315   return -1;
316 }
317
318 static void
319 hts_ts_disconnect_callback (session_t *s)
320 {
321   hts_main_t *htm = &hts_main;
322   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
323
324   a->handle = session_handle (s);
325   a->app_index = htm->app_index;
326   vnet_disconnect_session (a);
327 }
328
329 static void
330 hts_ts_reset_callback (session_t *s)
331 {
332   hts_main_t *htm = &hts_main;
333   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
334
335   a->handle = session_handle (s);
336   a->app_index = htm->app_index;
337   vnet_disconnect_session (a);
338 }
339
340 static void
341 hts_ts_cleanup_callback (session_t *s, session_cleanup_ntf_t ntf)
342 {
343   hts_session_t *hs;
344
345   if (ntf == SESSION_CLEANUP_TRANSPORT)
346     return;
347
348   hs = hts_session_get (s->thread_index, s->opaque);
349   if (!hs)
350     return;
351
352   hts_session_free (hs);
353 }
354
355 static int
356 hts_add_segment_callback (u32 client_index, u64 segment_handle)
357 {
358   return 0;
359 }
360
361 static int
362 hts_del_segment_callback (u32 client_index, u64 segment_handle)
363 {
364   return 0;
365 }
366
367 static session_cb_vft_t hs_session_cb_vft = {
368   .session_accept_callback = hts_ts_accept_callback,
369   .session_disconnect_callback = hts_ts_disconnect_callback,
370   .session_connected_callback = hts_ts_connected_callback,
371   .add_segment_callback = hts_add_segment_callback,
372   .del_segment_callback = hts_del_segment_callback,
373   .builtin_app_rx_callback = hts_ts_rx_callback,
374   .builtin_app_tx_callback = hs_ts_tx_callback,
375   .session_reset_callback = hts_ts_reset_callback,
376   .session_cleanup_callback = hts_ts_cleanup_callback,
377 };
378
379 static int
380 hts_attach (hts_main_t *hm)
381 {
382   vnet_app_add_cert_key_pair_args_t _ck_pair, *ck_pair = &_ck_pair;
383   u64 options[APP_OPTIONS_N_OPTIONS];
384   vnet_app_attach_args_t _a, *a = &_a;
385
386   clib_memset (a, 0, sizeof (*a));
387   clib_memset (options, 0, sizeof (options));
388
389   a->api_client_index = ~0;
390   a->name = format (0, "http_tps");
391   a->session_cb_vft = &hs_session_cb_vft;
392   a->options = options;
393   a->options[APP_OPTIONS_SEGMENT_SIZE] = hm->segment_size;
394   a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = hm->segment_size;
395   a->options[APP_OPTIONS_RX_FIFO_SIZE] = hm->fifo_size;
396   a->options[APP_OPTIONS_TX_FIFO_SIZE] = hm->fifo_size;
397   a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
398
399   if (vnet_application_attach (a))
400     {
401       vec_free (a->name);
402       clib_warning ("failed to attach server");
403       return -1;
404     }
405   vec_free (a->name);
406   hm->app_index = a->app_index;
407
408   clib_memset (ck_pair, 0, sizeof (*ck_pair));
409   ck_pair->cert = (u8 *) test_srv_crt_rsa;
410   ck_pair->key = (u8 *) test_srv_key_rsa;
411   ck_pair->cert_len = test_srv_crt_rsa_len;
412   ck_pair->key_len = test_srv_key_rsa_len;
413   vnet_app_add_cert_key_pair (ck_pair);
414   hm->ckpair_index = ck_pair->index;
415
416   return 0;
417 }
418
419 static int
420 hts_transport_needs_crypto (transport_proto_t proto)
421 {
422   return proto == TRANSPORT_PROTO_TLS || proto == TRANSPORT_PROTO_DTLS ||
423          proto == TRANSPORT_PROTO_QUIC;
424 }
425
426 static clib_error_t *
427 hts_listen (hts_main_t *htm, u8 *listen_uri, u8 is_del)
428 {
429   session_endpoint_cfg_t sep = SESSION_ENDPOINT_CFG_NULL;
430   vnet_listen_args_t _a, *a = &_a;
431   u8 need_crypto, *uri;
432   hts_session_t *hls;
433   session_t *ls;
434   uword *p;
435   int rv;
436
437   uri = listen_uri ? listen_uri : htm->default_uri;
438   p = hash_get_mem (htm->uri_to_handle, uri);
439
440   if (is_del)
441     {
442       if (!p)
443         return clib_error_return (0, "not listening on %v", uri);
444
445       hls = hts_session_get (0, *p);
446       ls = listen_session_get (hls->vpp_session_index);
447
448       vnet_unlisten_args_t ua = {
449         .handle = listen_session_get_handle (ls),
450         .app_index = htm->app_index,
451         .wrk_map_index = 0 /* default wrk */
452       };
453
454       hash_unset_mem (htm->uri_to_handle, uri);
455
456       if (vnet_unlisten (&ua))
457         return clib_error_return (0, "failed to unlisten");
458
459       vec_free (hls->uri);
460       hts_session_free (hls);
461
462       return 0;
463     }
464
465   if (p)
466     return clib_error_return (0, "already listening %v", uri);
467
468   if (parse_uri ((char *) uri, &sep))
469     return clib_error_return (0, "failed to parse uri %v", uri);
470
471   clib_memset (a, 0, sizeof (*a));
472   a->app_index = htm->app_index;
473
474   need_crypto = hts_transport_needs_crypto (sep.transport_proto);
475
476   sep.transport_proto = TRANSPORT_PROTO_HTTP;
477   clib_memcpy (&a->sep_ext, &sep, sizeof (sep));
478
479   if (need_crypto)
480     {
481       session_endpoint_alloc_ext_cfg (&a->sep_ext,
482                                       TRANSPORT_ENDPT_EXT_CFG_CRYPTO);
483       a->sep_ext.ext_cfg->crypto.ckpair_index = htm->ckpair_index;
484     }
485
486   rv = vnet_listen (a);
487
488   if (need_crypto)
489     clib_mem_free (a->sep_ext.ext_cfg);
490
491   if (rv)
492     return clib_error_return (0, "failed to listen on %v", uri);
493
494   hls = hts_session_alloc (0);
495   hls->uri = vec_dup (uri);
496   ls = listen_session_get_from_handle (a->handle);
497   hls->vpp_session_index = ls->session_index;
498   hash_set_mem (htm->uri_to_handle, hls->uri, hls->session_index);
499
500   return 0;
501 }
502
503 static int
504 hts_create (vlib_main_t *vm)
505 {
506   vlib_thread_main_t *vtm = vlib_get_thread_main ();
507   hts_main_t *htm = &hts_main;
508   u32 num_threads;
509
510   num_threads = 1 /* main thread */ + vtm->n_threads;
511   vec_validate (htm->sessions, num_threads - 1);
512
513   if (htm->no_zc)
514     vec_validate (htm->test_data, (64 << 10) - 1);
515
516   if (hts_attach (htm))
517     {
518       clib_warning ("failed to attach server");
519       return -1;
520     }
521
522   htm->default_uri = format (0, "tcp://0.0.0.0/80%c", 0);
523   htm->uri_to_handle = hash_create_vec (0, sizeof (u8), sizeof (uword));
524
525   return 0;
526 }
527
528 static clib_error_t *
529 hts_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
530                        vlib_cli_command_t *cmd)
531 {
532   unformat_input_t _line_input, *line_input = &_line_input;
533   hts_main_t *htm = &hts_main;
534   clib_error_t *error = 0;
535   u8 is_del = 0;
536   u64 mem_size;
537   u8 *uri = 0;
538
539   /* Get a line of input. */
540   if (!unformat_user (input, unformat_line_input, line_input))
541     goto start_server;
542
543   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
544     {
545       if (unformat (line_input, "private-segment-size %U",
546                     unformat_memory_size, &mem_size))
547         htm->segment_size = mem_size;
548       else if (unformat (line_input, "fifo-size %U", unformat_memory_size,
549                          &mem_size))
550         htm->fifo_size = mem_size;
551       else if (unformat (line_input, "uri %s", &uri))
552         ;
553       else if (unformat (line_input, "no-zc"))
554         htm->no_zc = 1;
555       else if (unformat (line_input, "debug"))
556         htm->debug_level = 1;
557       else if (unformat (line_input, "del"))
558         is_del = 1;
559       else
560         {
561           error = clib_error_return (0, "unknown input `%U'",
562                                      format_unformat_error, line_input);
563           break;
564         }
565     }
566
567   unformat_free (line_input);
568
569   if (error)
570     goto done;
571
572 start_server:
573
574   if (htm->app_index == (u32) ~0)
575     {
576       vnet_session_enable_disable (vm, 1 /* is_enable */);
577
578       if (hts_create (vm))
579         {
580           error = clib_error_return (0, "http tps create failed");
581           goto done;
582         }
583     }
584
585   error = hts_listen (htm, uri, is_del);
586
587 done:
588
589   vec_free (uri);
590   return error;
591 }
592
593 VLIB_CLI_COMMAND (http_tps_command, static) = {
594   .path = "http tps",
595   .short_help = "http tps [uri <uri>] [fifo-size <nbytes>] "
596                 "[segment-size <nMG>] [prealloc-fifos <n>] [debug] [no-zc] "
597                 "[del]",
598   .function = hts_create_command_fn,
599 };
600
601 static clib_error_t *
602 hts_show_command_fn (vlib_main_t *vm, unformat_input_t *input,
603                      vlib_cli_command_t *cmd)
604 {
605   unformat_input_t _line_input, *line_input = &_line_input;
606   hts_main_t *htm = &hts_main;
607   clib_error_t *error = 0;
608   u8 do_listeners = 0;
609   hts_session_t **sessions;
610   u32 n_listeners = 0, n_sessions = 0;
611
612   if (!unformat_user (input, unformat_line_input, line_input))
613     goto no_input;
614
615   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
616     {
617       if (unformat (line_input, "listeners"))
618         do_listeners = 1;
619       else
620         {
621           error = clib_error_return (0, "unknown input `%U'",
622                                      format_unformat_error, line_input);
623           break;
624         }
625     }
626
627   if (error)
628     return error;
629
630 no_input:
631
632   if (htm->app_index == ~0)
633     {
634       vlib_cli_output (vm, "http tps not enabled");
635       goto done;
636     }
637
638   if (do_listeners)
639     {
640       uword handle;
641       u8 *s = 0, *uri;
642
643       /* clang-format off */
644       hash_foreach (uri, handle, htm->uri_to_handle, ({
645         s = format (s, "%-30v%lx\n", uri, handle);
646       }));
647       /* clang-format on */
648
649       if (s)
650         {
651           vlib_cli_output (vm, "%-29s%s", "URI", "Index");
652           vlib_cli_output (vm, "%v", s);
653           vec_free (s);
654         }
655       goto done;
656     }
657
658   n_listeners = hash_elts (htm->uri_to_handle);
659   vec_foreach (sessions, htm->sessions)
660     n_sessions += pool_elts (*sessions);
661
662   vlib_cli_output (vm, " app index: %u\n listeners: %u\n sesions: %u",
663                    htm->app_index, n_listeners, n_sessions - n_listeners);
664
665 done:
666   return 0;
667 }
668
669 VLIB_CLI_COMMAND (show_http_tps_command, static) = {
670   .path = "show http tps",
671   .short_help = "http tps [listeners]",
672   .function = hts_show_command_fn,
673 };
674
675 static clib_error_t *
676 hs_main_init (vlib_main_t *vm)
677 {
678   hts_main_t *htm = &hts_main;
679
680   htm->app_index = ~0;
681   htm->segment_size = 128 << 20;
682   htm->fifo_size = 64 << 10;
683
684   return 0;
685 }
686
687 VLIB_INIT_FUNCTION (hs_main_init);
688
689 /*
690  * fd.io coding-style-patch-verification: ON
691  *
692  * Local Variables:
693  * eval: (c-set-style "gnu")
694  * End:
695  */