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