d742121ad2c69b5e5b79755636186afa81419a6b
[vpp.git] / src / plugins / hs_apps / http_cli.c
1 /*
2 * Copyright (c) 2017-2019 Cisco and/or its affiliates.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at:
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <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   u32 hs_index;
24   u32 thread_index;
25   u64 node_index;
26   u8 *buf;
27 } hcs_cli_args_t;
28
29 typedef struct
30 {
31   CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
32   u32 session_index;
33   u32 thread_index;
34   u8 *tx_buf;
35   u32 tx_offset;
36   u32 vpp_session_index;
37 } hcs_session_t;
38
39 typedef struct
40 {
41   hcs_session_t **sessions;
42   u32 *free_http_cli_process_node_indices;
43   u32 app_index;
44
45   /* Cert key pair for tls */
46   u32 ckpair_index;
47
48   u32 prealloc_fifos;
49   u32 private_segment_size;
50   u32 fifo_size;
51   u8 *uri;
52   vlib_main_t *vlib_main;
53 } hcs_main_t;
54
55 static hcs_main_t hcs_main;
56
57 static hcs_session_t *
58 hcs_session_alloc (u32 thread_index)
59 {
60   hcs_main_t *hcm = &hcs_main;
61   hcs_session_t *hs;
62   pool_get (hcm->sessions[thread_index], hs);
63   memset (hs, 0, sizeof (*hs));
64   hs->session_index = hs - hcm->sessions[thread_index];
65   hs->thread_index = thread_index;
66   return hs;
67 }
68
69 static hcs_session_t *
70 hcs_session_get (u32 thread_index, u32 hs_index)
71 {
72   hcs_main_t *hcm = &hcs_main;
73   if (pool_is_free_index (hcm->sessions[thread_index], hs_index))
74     return 0;
75   return pool_elt_at_index (hcm->sessions[thread_index], hs_index);
76 }
77
78 static void
79 hcs_session_free (hcs_session_t *hs)
80 {
81   hcs_main_t *hcm = &hcs_main;
82   u32 thread = hs->thread_index;
83   if (CLIB_DEBUG)
84     memset (hs, 0xfa, sizeof (*hs));
85   pool_put (hcm->sessions[thread], hs);
86 }
87
88 static void
89 hcs_cli_process_free (hcs_cli_args_t *args)
90 {
91   vlib_main_t *vm = vlib_get_first_main ();
92   hcs_main_t *hcm = &hcs_main;
93   hcs_cli_args_t **save_args;
94   vlib_node_runtime_t *rt;
95   vlib_node_t *n;
96   u32 node_index;
97
98   node_index = args->node_index;
99   ASSERT (node_index != 0);
100
101   n = vlib_get_node (vm, node_index);
102   rt = vlib_node_get_runtime (vm, n->index);
103   save_args = vlib_node_get_runtime_data (vm, n->index);
104
105   /* Reset process session pointer */
106   clib_mem_free (*save_args);
107   *save_args = 0;
108
109   /* Turn off the process node */
110   vlib_node_set_state (vm, rt->node_index, VLIB_NODE_STATE_DISABLED);
111
112   /* add node index to the freelist */
113   vec_add1 (hcm->free_http_cli_process_node_indices, node_index);
114 }
115
116 /* Header, including incantation to suppress favicon.ico requests */
117 static const char *html_header_template =
118     "<html><head><title>%v</title></head>"
119     "<link rel=\"icon\" href=\"data:,\">"
120     "<body><pre>";
121
122 static const char *html_footer =
123     "</pre></body></html>\r\n";
124
125 static void
126 hcs_cli_output (uword arg, u8 *buffer, uword buffer_bytes)
127 {
128   u8 **output_vecp = (u8 **) arg;
129   u8 *output_vec;
130   u32 offset;
131
132   output_vec = *output_vecp;
133
134   offset = vec_len (output_vec);
135   vec_validate (output_vec, offset + buffer_bytes - 1);
136   clib_memcpy_fast (output_vec + offset, buffer, buffer_bytes);
137
138   *output_vecp = output_vec;
139 }
140
141 static void
142 start_send_data (hcs_session_t *hs, http_status_code_t status)
143 {
144   http_msg_t msg;
145   session_t *ts;
146   int rv;
147
148   msg.type = HTTP_MSG_REPLY;
149   msg.code = status;
150   msg.content_type = HTTP_CONTENT_TEXT_HTML;
151   msg.data.type = HTTP_MSG_DATA_INLINE;
152   msg.data.len = vec_len (hs->tx_buf);
153
154   ts = session_get (hs->vpp_session_index, hs->thread_index);
155   rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
156   ASSERT (rv == sizeof (msg));
157
158   if (!msg.data.len)
159     goto done;
160
161   rv = svm_fifo_enqueue (ts->tx_fifo, vec_len (hs->tx_buf), hs->tx_buf);
162
163   if (rv != vec_len (hs->tx_buf))
164     {
165       hs->tx_offset = rv;
166       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
167     }
168   else
169     {
170       vec_free (hs->tx_buf);
171     }
172
173 done:
174
175   if (svm_fifo_set_event (ts->tx_fifo))
176     session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
177 }
178
179 static void
180 send_data_to_http (void *rpc_args)
181 {
182   hcs_cli_args_t *args = (hcs_cli_args_t *) rpc_args;
183   hcs_session_t *hs;
184
185   hs = hcs_session_get (args->thread_index, args->hs_index);
186   if (!hs)
187     {
188       vec_free (args->buf);
189       goto cleanup;
190     }
191
192   hs->tx_buf = args->buf;
193   start_send_data (hs, HTTP_STATUS_OK);
194
195 cleanup:
196
197   clib_mem_free (rpc_args);
198 }
199
200 static uword
201 hcs_cli_process (vlib_main_t *vm, vlib_node_runtime_t *rt, vlib_frame_t *f)
202 {
203   u8 *request = 0, *reply = 0, *html = 0;
204   hcs_cli_args_t *args, *rpc_args;
205   hcs_main_t *hcm = &hcs_main;
206   hcs_cli_args_t **save_args;
207   unformat_input_t input;
208   int i;
209
210   save_args = vlib_node_get_runtime_data (hcm->vlib_main, rt->node_index);
211   args = *save_args;
212
213   request = args->buf;
214
215   /* Replace slashes with spaces, stop at the end of the path */
216   i = 0;
217   while (i < vec_len (request))
218     {
219       if (request[i] == '/')
220         request[i] = ' ';
221       else if (request[i] == ' ')
222         {
223           /* vlib_cli_input is vector-based, no need for a NULL */
224           vec_set_len (request, i);
225           break;
226         }
227       i++;
228     }
229
230   /* Generate the html header */
231   html = format (0, html_header_template, request /* title */ );
232
233   /* Run the command */
234   unformat_init_vector (&input, vec_dup (request));
235   vlib_cli_input (vm, &input, hcs_cli_output, (uword) &reply);
236   unformat_free (&input);
237   request = 0;
238
239   /* Generate the html page */
240   html = format (html, "%v", reply);
241   html = format (html, html_footer);
242
243   /* Send it */
244   rpc_args = clib_mem_alloc (sizeof (*args));
245   clib_memcpy_fast (rpc_args, args, sizeof (*args));
246   rpc_args->buf = html;
247
248   session_send_rpc_evt_to_thread_force (args->thread_index, send_data_to_http,
249                                         rpc_args);
250
251   vec_free (reply);
252   vec_free (args->buf);
253   hcs_cli_process_free (args);
254
255   return (0);
256 }
257
258 static void
259 alloc_cli_process (hcs_cli_args_t *args)
260 {
261   hcs_main_t *hcm = &hcs_main;
262   vlib_main_t *vm = hcm->vlib_main;
263   hcs_cli_args_t **save_args;
264   vlib_node_t *n;
265   uword l;
266
267   l = vec_len (hcm->free_http_cli_process_node_indices);
268   if (l > 0)
269     {
270       n = vlib_get_node (vm, hcm->free_http_cli_process_node_indices[l - 1]);
271       vlib_node_set_state (vm, n->index, VLIB_NODE_STATE_POLLING);
272       vec_set_len (hcm->free_http_cli_process_node_indices, l - 1);
273     }
274   else
275     {
276       static vlib_node_registration_t r = {
277         .function = hcs_cli_process,
278         .type = VLIB_NODE_TYPE_PROCESS,
279         .process_log2_n_stack_bytes = 16,
280         .runtime_data_bytes = sizeof (void *),
281       };
282
283       vlib_register_node (vm, &r, "http-cli-%d", l);
284
285       n = vlib_get_node (vm, r.index);
286     }
287
288   /* Save the node index in the args. It won't be zero. */
289   args->node_index = n->index;
290
291   /* Save the args (pointer) in the node runtime */
292   save_args = vlib_node_get_runtime_data (vm, n->index);
293   *save_args = clib_mem_alloc (sizeof (*args));
294   clib_memcpy_fast (*save_args, args, sizeof (*args));
295
296   vlib_start_process (vm, n->runtime_index);
297 }
298
299 static void
300 alloc_cli_process_callback (void *cb_args)
301 {
302   alloc_cli_process ((hcs_cli_args_t *) cb_args);
303 }
304
305 static int
306 hcs_ts_rx_callback (session_t *ts)
307 {
308   hcs_cli_args_t args = {};
309   hcs_session_t *hs;
310   http_msg_t msg;
311   int rv;
312
313   hs = hcs_session_get (ts->thread_index, ts->opaque);
314
315   /* Read the http message header */
316   rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
317   ASSERT (rv == sizeof (msg));
318
319   if (msg.type != HTTP_MSG_REQUEST || msg.method_type != HTTP_REQ_GET)
320     {
321       hs->tx_buf = 0;
322       start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED);
323       return 0;
324     }
325
326   /* send the command to a new/recycled vlib process */
327   vec_validate (args.buf, msg.data.len - 1);
328   rv = svm_fifo_dequeue (ts->rx_fifo, msg.data.len, args.buf);
329   ASSERT (rv == msg.data.len);
330   vec_set_len (args.buf, rv);
331
332   args.hs_index = hs->session_index;
333   args.thread_index = ts->thread_index;
334
335   /* Send RPC request to main thread */
336   if (vlib_get_thread_index () != 0)
337     vlib_rpc_call_main_thread (alloc_cli_process_callback, (u8 *) &args,
338                                sizeof (args));
339   else
340     alloc_cli_process (&args);
341   return 0;
342 }
343
344 static int
345 hcs_ts_tx_callback (session_t *ts)
346 {
347   hcs_session_t *hs;
348   u32 to_send;
349   int rv;
350
351   hs = hcs_session_get (ts->thread_index, ts->opaque);
352   if (!hs || !hs->tx_buf)
353     return 0;
354
355   to_send = vec_len (hs->tx_buf) - hs->tx_offset;
356   rv = svm_fifo_enqueue (ts->tx_fifo, to_send, hs->tx_buf + hs->tx_offset);
357
358   if (rv <= 0)
359     {
360       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
361       return 0;
362     }
363
364   if (rv < to_send)
365     {
366       hs->tx_offset += rv;
367       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
368     }
369   else
370     {
371       vec_free (hs->tx_buf);
372     }
373
374   if (svm_fifo_set_event (ts->tx_fifo))
375     session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
376
377   return 0;
378 }
379
380 static int
381 hcs_ts_accept_callback (session_t *ts)
382 {
383   hcs_session_t *hs;
384
385   hs = hcs_session_alloc (ts->thread_index);
386   hs->vpp_session_index = ts->session_index;
387
388   ts->opaque = hs->session_index;
389   ts->session_state = SESSION_STATE_READY;
390
391   return 0;
392 }
393
394 static int
395 hcs_ts_connected_callback (u32 app_index, u32 api_context, session_t *s,
396                            session_error_t err)
397 {
398   clib_warning ("called...");
399   return -1;
400 }
401
402 static void
403 hcs_ts_disconnect_callback (session_t *s)
404 {
405   hcs_main_t *hcm = &hcs_main;
406   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
407
408   a->handle = session_handle (s);
409   a->app_index = hcm->app_index;
410   vnet_disconnect_session (a);
411 }
412
413 static void
414 hcs_ts_reset_callback (session_t *s)
415 {
416   hcs_main_t *hcm = &hcs_main;
417   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
418
419   a->handle = session_handle (s);
420   a->app_index = hcm->app_index;
421   vnet_disconnect_session (a);
422 }
423
424 static void
425 hcs_ts_cleanup_callback (session_t *s, session_cleanup_ntf_t ntf)
426 {
427   hcs_session_t *hs;
428
429   if (ntf == SESSION_CLEANUP_TRANSPORT)
430     return;
431
432   hs = hcs_session_get (s->thread_index, s->opaque);
433   if (!hs)
434     return;
435
436   vec_free (hs->tx_buf);
437   hcs_session_free (hs);
438 }
439
440 static int
441 hcs_add_segment_callback (u32 client_index, u64 segment_handle)
442 {
443   return 0;
444 }
445
446 static int
447 hcs_del_segment_callback (u32 client_index, u64 segment_handle)
448 {
449   return 0;
450 }
451
452 static session_cb_vft_t hcs_session_cb_vft = {
453   .session_accept_callback = hcs_ts_accept_callback,
454   .session_disconnect_callback = hcs_ts_disconnect_callback,
455   .session_connected_callback = hcs_ts_connected_callback,
456   .add_segment_callback = hcs_add_segment_callback,
457   .del_segment_callback = hcs_del_segment_callback,
458   .builtin_app_rx_callback = hcs_ts_rx_callback,
459   .builtin_app_tx_callback = hcs_ts_tx_callback,
460   .session_reset_callback = hcs_ts_reset_callback,
461   .session_cleanup_callback = hcs_ts_cleanup_callback,
462 };
463
464 static int
465 hcs_attach ()
466 {
467   vnet_app_add_cert_key_pair_args_t _ck_pair, *ck_pair = &_ck_pair;
468   hcs_main_t *hcm = &hcs_main;
469   u64 options[APP_OPTIONS_N_OPTIONS];
470   vnet_app_attach_args_t _a, *a = &_a;
471   u32 segment_size = 128 << 20;
472
473   clib_memset (a, 0, sizeof (*a));
474   clib_memset (options, 0, sizeof (options));
475
476   if (hcm->private_segment_size)
477     segment_size = hcm->private_segment_size;
478
479   a->api_client_index = ~0;
480   a->name = format (0, "http_cli_server");
481   a->session_cb_vft = &hcs_session_cb_vft;
482   a->options = options;
483   a->options[APP_OPTIONS_SEGMENT_SIZE] = segment_size;
484   a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = segment_size;
485   a->options[APP_OPTIONS_RX_FIFO_SIZE] =
486     hcm->fifo_size ? hcm->fifo_size : 8 << 10;
487   a->options[APP_OPTIONS_TX_FIFO_SIZE] =
488     hcm->fifo_size ? hcm->fifo_size : 32 << 10;
489   a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
490   a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = hcm->prealloc_fifos;
491
492   if (vnet_application_attach (a))
493     {
494       vec_free (a->name);
495       clib_warning ("failed to attach server");
496       return -1;
497     }
498   vec_free (a->name);
499   hcm->app_index = a->app_index;
500
501   clib_memset (ck_pair, 0, sizeof (*ck_pair));
502   ck_pair->cert = (u8 *) test_srv_crt_rsa;
503   ck_pair->key = (u8 *) test_srv_key_rsa;
504   ck_pair->cert_len = test_srv_crt_rsa_len;
505   ck_pair->key_len = test_srv_key_rsa_len;
506   vnet_app_add_cert_key_pair (ck_pair);
507   hcm->ckpair_index = ck_pair->index;
508
509   return 0;
510 }
511
512 static int
513 hcs_transport_needs_crypto (transport_proto_t proto)
514 {
515   return proto == TRANSPORT_PROTO_TLS || proto == TRANSPORT_PROTO_DTLS ||
516          proto == TRANSPORT_PROTO_QUIC;
517 }
518
519 static int
520 hcs_listen ()
521 {
522   session_endpoint_cfg_t sep = SESSION_ENDPOINT_CFG_NULL;
523   hcs_main_t *hcm = &hcs_main;
524   vnet_listen_args_t _a, *a = &_a;
525   char *uri = "tcp://0.0.0.0/80";
526   u8 need_crypto;
527   int rv;
528
529   clib_memset (a, 0, sizeof (*a));
530   a->app_index = hcm->app_index;
531
532   if (hcm->uri)
533     uri = (char *) hcm->uri;
534
535   if (parse_uri (uri, &sep))
536     return -1;
537
538   need_crypto = hcs_transport_needs_crypto (sep.transport_proto);
539
540   sep.transport_proto = TRANSPORT_PROTO_HTTP;
541   clib_memcpy (&a->sep_ext, &sep, sizeof (sep));
542
543   if (need_crypto)
544     {
545       session_endpoint_alloc_ext_cfg (&a->sep_ext,
546                                       TRANSPORT_ENDPT_EXT_CFG_CRYPTO);
547       a->sep_ext.ext_cfg->crypto.ckpair_index = hcm->ckpair_index;
548     }
549
550   rv = vnet_listen (a);
551
552   if (need_crypto)
553     clib_mem_free (a->sep_ext.ext_cfg);
554
555   return rv;
556 }
557
558 static int
559 hcs_create (vlib_main_t *vm)
560 {
561   vlib_thread_main_t *vtm = vlib_get_thread_main ();
562   hcs_main_t *hcm = &hcs_main;
563   u32 num_threads;
564
565   num_threads = 1 /* main thread */  + vtm->n_threads;
566   vec_validate (hcm->sessions, num_threads - 1);
567
568   if (hcs_attach ())
569     {
570       clib_warning ("failed to attach server");
571       return -1;
572     }
573   if (hcs_listen ())
574     {
575       clib_warning ("failed to start listening");
576       return -1;
577     }
578
579   return 0;
580 }
581
582 static clib_error_t *
583 hcs_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
584                        vlib_cli_command_t *cmd)
585 {
586   unformat_input_t _line_input, *line_input = &_line_input;
587   hcs_main_t *hcm = &hcs_main;
588   u64 seg_size;
589   int rv;
590
591   hcm->prealloc_fifos = 0;
592   hcm->private_segment_size = 0;
593   hcm->fifo_size = 0;
594
595   /* Get a line of input. */
596   if (!unformat_user (input, unformat_line_input, line_input))
597     goto start_server;
598
599   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
600     {
601       if (unformat (line_input, "prealloc-fifos %d", &hcm->prealloc_fifos))
602         ;
603       else if (unformat (line_input, "private-segment-size %U",
604                          unformat_memory_size, &seg_size))
605         hcm->private_segment_size = seg_size;
606       else if (unformat (line_input, "fifo-size %d", &hcm->fifo_size))
607         hcm->fifo_size <<= 10;
608       else if (unformat (line_input, "uri %s", &hcm->uri))
609         ;
610       else
611         {
612           unformat_free (line_input);
613           return clib_error_return (0, "unknown input `%U'",
614                                     format_unformat_error, line_input);
615         }
616     }
617
618   unformat_free (line_input);
619
620 start_server:
621
622   if (hcm->app_index != (u32) ~0)
623     return clib_error_return (0, "test http server is already running");
624
625   vnet_session_enable_disable (vm, 1 /* turn on TCP, etc. */ );
626
627   rv = hcs_create (vm);
628   switch (rv)
629     {
630     case 0:
631       break;
632     default:
633       return clib_error_return (0, "server_create returned %d", rv);
634     }
635
636   return 0;
637 }
638
639 VLIB_CLI_COMMAND (hcs_create_command, static) = {
640   .path = "http cli server",
641   .short_help = "http cli server [uri <uri>] [fifo-size <nbytes>] "
642                 "[private-segment-size <nMG>] [prealloc-fifos <n>]",
643   .function = hcs_create_command_fn,
644 };
645
646 static clib_error_t *
647 hcs_main_init (vlib_main_t *vm)
648 {
649   hcs_main_t *hcs = &hcs_main;
650
651   hcs->app_index = ~0;
652   hcs->vlib_main = vm;
653   return 0;
654 }
655
656 VLIB_INIT_FUNCTION (hcs_main_init);
657
658 /*
659 * fd.io coding-style-patch-verification: ON
660 *
661 * Local Variables:
662 * eval: (c-set-style "gnu")
663 * End:
664 */