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:
7 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 #include <vnet/session/application.h>
17 #include <vnet/session/application_interface.h>
18 #include <vnet/session/session.h>
19 #include <http/http.h>
23 CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
28 u32 vpp_session_index;
31 /** threshold after which connection is closed */
33 /** rate at which accepted sessions are marked for random close */
39 typedef struct hts_listen_cfg_
47 typedef struct hs_main_
49 hts_session_t **sessions;
55 /** Hash table of listener uris to handles */
70 static hts_main_t hts_main;
72 static hts_session_t *
73 hts_session_alloc (u32 thread_index)
75 hts_main_t *htm = &hts_main;
78 pool_get_zero (htm->sessions[thread_index], hs);
79 hs->session_index = hs - htm->sessions[thread_index];
80 hs->thread_index = thread_index;
85 static hts_session_t *
86 hts_session_get (u32 thread_index, u32 hts_index)
88 hts_main_t *htm = &hts_main;
90 if (pool_is_free_index (htm->sessions[thread_index], hts_index))
93 return pool_elt_at_index (htm->sessions[thread_index], hts_index);
97 hts_session_free (hts_session_t *hs)
99 hts_main_t *htm = &hts_main;
100 u32 thread = hs->thread_index;
102 if (htm->debug_level > 0)
103 clib_warning ("Freeing session %u", hs->session_index);
106 clib_memset (hs, 0xfa, sizeof (*hs));
108 pool_put (htm->sessions[thread], hs);
112 hts_disconnect_transport (hts_session_t *hs)
114 vnet_disconnect_args_t _a = { 0 }, *a = &_a;
115 hts_main_t *htm = &hts_main;
118 if (htm->debug_level > 0)
119 clib_warning ("Actively closing session %u", hs->session_index);
121 ts = session_get (hs->vpp_session_index, hs->thread_index);
122 a->handle = session_handle (ts);
123 a->app_index = htm->app_index;
124 vnet_disconnect_session (a);
128 hts_session_tx_zc (hts_session_t *hs, session_t *ts)
134 rv = svm_fifo_fill_chunk_list (ts->tx_fifo);
137 svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
141 max_send = hs->data_len - hs->data_offset;
142 space = svm_fifo_max_enqueue (ts->tx_fifo);
144 to_send = clib_min (space, max_send);
146 svm_fifo_enqueue_nocopy (ts->tx_fifo, to_send);
148 hs->data_offset += to_send;
150 if (to_send < max_send)
151 svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
153 if (svm_fifo_set_event (ts->tx_fifo))
154 session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
158 hts_session_tx_no_zc (hts_session_t *hs, session_t *ts)
160 u32 n_segs, buf_offset, buf_left;
161 u64 max_send = 32 << 10, left;
162 hts_main_t *htm = &hts_main;
163 svm_fifo_seg_t seg[2];
166 left = hs->data_len - hs->data_offset;
167 max_send = clib_min (left, max_send);
168 buf_offset = hs->data_offset % vec_len (htm->test_data);
169 buf_left = vec_len (htm->test_data) - buf_offset;
171 if (buf_left < max_send)
173 seg[0].data = htm->test_data + buf_offset;
174 seg[0].len = buf_left;
175 seg[1].data = htm->test_data;
176 seg[1].len = max_send - buf_left;
181 seg[0].data = htm->test_data + buf_offset;
182 seg[0].len = max_send;
186 sent = svm_fifo_enqueue_segments (ts->tx_fifo, seg, n_segs,
187 1 /* allow partial */);
191 svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
195 hs->data_offset += sent;
198 svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
200 if (svm_fifo_set_event (ts->tx_fifo))
201 session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
205 hts_session_tx (hts_session_t *hs, session_t *ts)
207 hts_main_t *htm = &hts_main;
210 hts_session_tx_zc (hs, ts);
212 hts_session_tx_no_zc (hs, ts);
214 if (hs->close_threshold > 0)
216 if ((f64) hs->data_offset / hs->data_len > hs->close_threshold)
217 hts_disconnect_transport (hs);
222 hts_start_send_data (hts_session_t *hs, http_status_code_t status)
228 msg.type = HTTP_MSG_REPLY;
230 msg.content_type = HTTP_CONTENT_APP_OCTET_STREAM;
231 msg.data.type = HTTP_MSG_DATA_INLINE;
232 msg.data.len = hs->data_len;
234 ts = session_get (hs->vpp_session_index, hs->thread_index);
235 rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
236 ASSERT (rv == sizeof (msg));
240 if (svm_fifo_set_event (ts->tx_fifo))
241 session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
245 hts_session_tx (hs, ts);
249 try_test_file (hts_session_t *hs, u8 *target)
251 char *test_str = "test_file";
252 hts_main_t *htm = &hts_main;
253 unformat_input_t input;
257 if (memcmp (target, test_str, clib_strnlen (test_str, 9)))
260 unformat_init_vector (&input, vec_dup (target));
261 if (!unformat (&input, "test_file_%U", unformat_memory_size, &file_size))
267 if (unformat_check_input (&input) != UNFORMAT_END_OF_INPUT)
273 if (htm->debug_level)
274 clib_warning ("Requested file size %U", format_memory_size, file_size);
276 hs->data_len = file_size;
279 if (hs->close_threshold > 0)
281 /* Disconnect if the header is already enough to fill the quota */
282 if ((f64) 30 / hs->data_len > hs->close_threshold)
284 hts_disconnect_transport (hs);
289 hts_start_send_data (hs, HTTP_STATUS_OK);
292 unformat_free (&input);
298 hts_ts_rx_callback (session_t *ts)
300 hts_main_t *htm = &hts_main;
306 hs = hts_session_get (ts->thread_index, ts->opaque);
308 /* Read the http message header */
309 rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
310 ASSERT (rv == sizeof (msg));
312 if (msg.type != HTTP_MSG_REQUEST || msg.method_type != HTTP_REQ_GET)
314 hts_start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED);
318 if (msg.data.target_path_len == 0 ||
319 msg.data.target_form != HTTP_TARGET_ORIGIN_FORM)
321 hts_start_send_data (hs, HTTP_STATUS_BAD_REQUEST);
325 vec_validate (target, msg.data.target_path_len - 1);
326 rv = svm_fifo_peek (ts->rx_fifo, msg.data.target_path_offset,
327 msg.data.target_path_len, target);
328 ASSERT (rv == msg.data.target_path_len);
330 if (htm->debug_level)
331 clib_warning ("Request target: %v", target);
333 if (try_test_file (hs, target))
334 hts_start_send_data (hs, HTTP_STATUS_NOT_FOUND);
339 svm_fifo_dequeue_drop (ts->rx_fifo, msg.data.len);
344 hs_ts_tx_callback (session_t *ts)
348 hs = hts_session_get (ts->thread_index, ts->opaque);
352 hts_session_tx (hs, ts);
358 hts_ts_accept_callback (session_t *ts)
360 hts_main_t *htm = &hts_main;
361 hts_session_t *hs, *lhs;
364 hs = hts_session_alloc (ts->thread_index);
365 hs->vpp_session_index = ts->session_index;
367 ts->opaque = hs->session_index;
368 ts->session_state = SESSION_STATE_READY;
370 /* Check if listener configured for random closes */
371 ls = listen_session_get_from_handle (ts->listener_handle);
372 lhs = hts_session_get (0, ls->opaque);
376 /* overload listener's data_offset as session counter */
377 u32 cnt = __atomic_add_fetch (&lhs->data_offset, 1, __ATOMIC_RELEASE);
378 if ((cnt % lhs->close_rate) == 0)
379 hs->close_threshold = random_f64 (&htm->seed);
382 if (htm->debug_level > 0)
383 clib_warning ("Accepted session %u close threshold %.2f", ts->opaque,
384 hs->close_threshold);
390 hts_ts_connected_callback (u32 app_index, u32 api_context, session_t *s,
393 clib_warning ("called...");
398 hts_ts_disconnect_callback (session_t *ts)
400 hts_main_t *htm = &hts_main;
401 vnet_disconnect_args_t _a = { 0 }, *a = &_a;
403 if (htm->debug_level > 0)
404 clib_warning ("Transport closing session %u", ts->opaque);
406 a->handle = session_handle (ts);
407 a->app_index = htm->app_index;
408 vnet_disconnect_session (a);
412 hts_ts_reset_callback (session_t *ts)
414 hts_main_t *htm = &hts_main;
415 vnet_disconnect_args_t _a = { 0 }, *a = &_a;
417 if (htm->debug_level > 0)
418 clib_warning ("Transport reset session %u", ts->opaque);
420 a->handle = session_handle (ts);
421 a->app_index = htm->app_index;
422 vnet_disconnect_session (a);
426 hts_ts_cleanup_callback (session_t *s, session_cleanup_ntf_t ntf)
430 if (ntf == SESSION_CLEANUP_TRANSPORT)
433 hs = hts_session_get (s->thread_index, s->opaque);
437 hts_session_free (hs);
441 hts_add_segment_callback (u32 client_index, u64 segment_handle)
447 hts_del_segment_callback (u32 client_index, u64 segment_handle)
452 static session_cb_vft_t hs_session_cb_vft = {
453 .session_accept_callback = hts_ts_accept_callback,
454 .session_disconnect_callback = hts_ts_disconnect_callback,
455 .session_connected_callback = hts_ts_connected_callback,
456 .add_segment_callback = hts_add_segment_callback,
457 .del_segment_callback = hts_del_segment_callback,
458 .builtin_app_rx_callback = hts_ts_rx_callback,
459 .builtin_app_tx_callback = hs_ts_tx_callback,
460 .session_reset_callback = hts_ts_reset_callback,
461 .session_cleanup_callback = hts_ts_cleanup_callback,
465 hts_attach (hts_main_t *hm)
467 vnet_app_add_cert_key_pair_args_t _ck_pair, *ck_pair = &_ck_pair;
468 u64 options[APP_OPTIONS_N_OPTIONS];
469 vnet_app_attach_args_t _a, *a = &_a;
471 clib_memset (a, 0, sizeof (*a));
472 clib_memset (options, 0, sizeof (options));
474 a->api_client_index = ~0;
475 a->name = format (0, "http_tps");
476 a->session_cb_vft = &hs_session_cb_vft;
477 a->options = options;
478 a->options[APP_OPTIONS_SEGMENT_SIZE] = hm->segment_size;
479 a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = hm->segment_size;
480 a->options[APP_OPTIONS_RX_FIFO_SIZE] = hm->fifo_size;
481 a->options[APP_OPTIONS_TX_FIFO_SIZE] = hm->fifo_size;
482 a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
484 if (vnet_application_attach (a))
487 clib_warning ("failed to attach server");
491 hm->app_index = a->app_index;
493 clib_memset (ck_pair, 0, sizeof (*ck_pair));
494 ck_pair->cert = (u8 *) test_srv_crt_rsa;
495 ck_pair->key = (u8 *) test_srv_key_rsa;
496 ck_pair->cert_len = test_srv_crt_rsa_len;
497 ck_pair->key_len = test_srv_key_rsa_len;
498 vnet_app_add_cert_key_pair (ck_pair);
499 hm->ckpair_index = ck_pair->index;
505 hts_transport_needs_crypto (transport_proto_t proto)
507 return proto == TRANSPORT_PROTO_TLS || proto == TRANSPORT_PROTO_DTLS ||
508 proto == TRANSPORT_PROTO_QUIC;
512 hts_start_listen (hts_main_t *htm, session_endpoint_cfg_t *sep, u8 *uri,
515 vnet_listen_args_t _a, *a = &_a;
519 u32 thread_index = 0;
522 clib_memset (a, 0, sizeof (*a));
523 a->app_index = htm->app_index;
525 need_crypto = hts_transport_needs_crypto (sep->transport_proto);
527 sep->transport_proto = TRANSPORT_PROTO_HTTP;
528 clib_memcpy (&a->sep_ext, sep, sizeof (*sep));
532 session_endpoint_alloc_ext_cfg (&a->sep_ext,
533 TRANSPORT_ENDPT_EXT_CFG_CRYPTO);
534 a->sep_ext.ext_cfg->crypto.ckpair_index = htm->ckpair_index;
537 rv = vnet_listen (a);
540 clib_mem_free (a->sep_ext.ext_cfg);
545 hls = hts_session_alloc (thread_index);
546 hls->uri = vec_dup (uri);
547 hls->close_rate = (f64) 1 / rnd_close;
548 ls = listen_session_get_from_handle (a->handle);
549 hls->vpp_session_index = ls->session_index;
550 hash_set_mem (htm->uri_to_handle, hls->uri, hls->session_index);
552 /* opaque holds index of hls, which is used in `hts_ts_accept_callback`
553 * to get back the pointer to hls */
554 ls->opaque = hls - htm->sessions[thread_index];
560 hts_stop_listen (hts_main_t *htm, u32 hls_index)
565 hls = hts_session_get (0, hls_index);
566 ls = listen_session_get (hls->vpp_session_index);
568 vnet_unlisten_args_t ua = {
569 .handle = listen_session_get_handle (ls),
570 .app_index = htm->app_index,
571 .wrk_map_index = 0 /* default wrk */
574 hash_unset_mem (htm->uri_to_handle, hls->uri);
576 if (vnet_unlisten (&ua))
580 hts_session_free (hls);
585 static clib_error_t *
586 hts_listen (hts_main_t *htm, hts_listen_cfg_t *lcfg)
588 session_endpoint_cfg_t sep = SESSION_ENDPOINT_CFG_NULL;
589 clib_error_t *error = 0;
594 uri = lcfg->uri ? lcfg->uri : htm->default_uri;
595 uri_key = format (0, "vrf%u-%s", lcfg->vrf, uri);
596 p = hash_get_mem (htm->uri_to_handle, uri_key);
601 error = clib_error_return (0, "not listening on %v", uri);
602 else if (hts_stop_listen (htm, p[0]))
603 error = clib_error_return (0, "failed to unlisten");
609 error = clib_error_return (0, "already listening %v", uri);
613 if (parse_uri ((char *) uri, &sep))
615 error = clib_error_return (0, "failed to parse uri %v", uri);
624 fp = sep.is_ip4 ? FIB_PROTOCOL_IP4 : FIB_PROTOCOL_IP6;
625 fib_index = fib_table_find (fp, lcfg->vrf);
628 error = clib_error_return (0, "no such vrf %u", lcfg->vrf);
631 sep.fib_index = fib_index;
634 if ((rv = hts_start_listen (htm, &sep, uri_key, lcfg->rnd_close)))
636 error = clib_error_return (0, "failed to listen on %v: %U", uri,
637 format_session_error, rv);
647 hts_create (vlib_main_t *vm)
649 vlib_thread_main_t *vtm = vlib_get_thread_main ();
650 hts_main_t *htm = &hts_main;
653 num_threads = 1 /* main thread */ + vtm->n_threads;
654 vec_validate (htm->sessions, num_threads - 1);
657 vec_validate (htm->test_data, (64 << 10) - 1);
659 if (hts_attach (htm))
661 clib_warning ("failed to attach server");
665 htm->default_uri = format (0, "tcp://0.0.0.0/80%c", 0);
666 htm->uri_to_handle = hash_create_vec (0, sizeof (u8), sizeof (uword));
671 static clib_error_t *
672 hts_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
673 vlib_cli_command_t *cmd)
675 unformat_input_t _line_input, *line_input = &_line_input;
676 hts_main_t *htm = &hts_main;
677 hts_listen_cfg_t lcfg = {};
678 clib_error_t *error = 0;
681 /* Get a line of input. */
682 if (!unformat_user (input, unformat_line_input, line_input))
685 while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
687 if (unformat (line_input, "private-segment-size %U",
688 unformat_memory_size, &mem_size))
689 htm->segment_size = mem_size;
690 else if (unformat (line_input, "fifo-size %U", unformat_memory_size,
692 htm->fifo_size = mem_size;
693 else if (unformat (line_input, "no-zc"))
695 else if (unformat (line_input, "debug"))
696 htm->debug_level = 1;
697 else if (unformat (line_input, "vrf %u", &lcfg.vrf))
699 else if (unformat (line_input, "uri %s", &lcfg.uri))
701 else if (unformat (line_input, "rnd-close %f", &lcfg.rnd_close))
703 if (lcfg.rnd_close > 1.0)
705 error = clib_error_return (0, "invalid rnd close value %f",
710 else if (unformat (line_input, "del"))
714 error = clib_error_return (0, "unknown input `%U'",
715 format_unformat_error, line_input);
720 unformat_free (line_input);
727 if (htm->app_index == (u32) ~0)
729 vnet_session_enable_disable (vm, 1 /* is_enable */);
733 error = clib_error_return (0, "http tps create failed");
738 error = hts_listen (htm, &lcfg);
746 VLIB_CLI_COMMAND (http_tps_command, static) = {
748 .short_help = "http tps [uri <uri>] [fifo-size <nbytes>] "
749 "[segment-size <nMG>] [prealloc-fifos <n>] [debug] [no-zc] "
751 .function = hts_create_command_fn,
754 static clib_error_t *
755 hts_show_command_fn (vlib_main_t *vm, unformat_input_t *input,
756 vlib_cli_command_t *cmd)
758 unformat_input_t _line_input, *line_input = &_line_input;
759 hts_main_t *htm = &hts_main;
760 clib_error_t *error = 0;
762 hts_session_t **sessions;
763 u32 n_listeners = 0, n_sessions = 0;
765 if (!unformat_user (input, unformat_line_input, line_input))
768 while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
770 if (unformat (line_input, "listeners"))
774 error = clib_error_return (0, "unknown input `%U'",
775 format_unformat_error, line_input);
785 if (htm->app_index == ~0)
787 vlib_cli_output (vm, "http tps not enabled");
796 /* clang-format off */
797 hash_foreach (uri, handle, htm->uri_to_handle, ({
798 s = format (s, "%-30v%lx\n", uri, handle);
800 /* clang-format on */
804 vlib_cli_output (vm, "%-29s%s", "URI", "Index");
805 vlib_cli_output (vm, "%v", s);
811 n_listeners = hash_elts (htm->uri_to_handle);
812 vec_foreach (sessions, htm->sessions)
813 n_sessions += pool_elts (*sessions);
815 vlib_cli_output (vm, " app index: %u\n listeners: %u\n sesions: %u",
816 htm->app_index, n_listeners, n_sessions - n_listeners);
822 VLIB_CLI_COMMAND (show_http_tps_command, static) = {
823 .path = "show http tps",
824 .short_help = "http tps [listeners]",
825 .function = hts_show_command_fn,
828 static clib_error_t *
829 hs_main_init (vlib_main_t *vm)
831 hts_main_t *htm = &hts_main;
834 htm->segment_size = 128 << 20;
835 htm->fifo_size = 64 << 10;
840 VLIB_INIT_FUNCTION (hs_main_init);
843 * fd.io coding-style-patch-verification: ON
846 * eval: (c-set-style "gnu")