bfd: restore the data within the packet after hash verification
[vpp.git] / src / plugins / http_static / static_server.c
1 /*
2  * Copyright (c) 2017-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 <http_static/http_static.h>
17 #include <vppinfra/bihash_template.c>
18 #include <vppinfra/unix.h>
19
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <unistd.h>
23
24 /** @file static_server.c
25  *  Static http server, sufficient to serve .html / .css / .js content.
26  */
27 /*? %%clicmd:group_label Static HTTP Server %% ?*/
28
29 #define HSS_FIFO_THRESH (16 << 10)
30
31 hss_main_t hss_main;
32
33 static void
34 hss_cache_lock (void)
35 {
36   clib_spinlock_lock (&hss_main.cache_lock);
37 }
38
39 static void
40 hss_cache_unlock (void)
41 {
42   clib_spinlock_unlock (&hss_main.cache_lock);
43 }
44
45 static hss_session_t *
46 hss_session_alloc (u32 thread_index)
47 {
48   hss_main_t *hsm = &hss_main;
49   hss_session_t *hs;
50
51   pool_get_zero (hsm->sessions[thread_index], hs);
52   hs->session_index = hs - hsm->sessions[thread_index];
53   hs->thread_index = thread_index;
54   hs->cache_pool_index = ~0;
55   return hs;
56 }
57
58 static hss_session_t *
59 hss_session_get (u32 thread_index, u32 hs_index)
60 {
61   hss_main_t *hsm = &hss_main;
62   if (pool_is_free_index (hsm->sessions[thread_index], hs_index))
63     return 0;
64   return pool_elt_at_index (hsm->sessions[thread_index], hs_index);
65 }
66
67 static void
68 hss_session_free (hss_session_t *hs)
69 {
70   hss_main_t *hsm = &hss_main;
71
72   pool_put (hsm->sessions[hs->thread_index], hs);
73
74   if (CLIB_DEBUG)
75     {
76       u32 save_thread_index;
77       save_thread_index = hs->thread_index;
78       /* Poison the entry, preserve timer state and thread index */
79       memset (hs, 0xfa, sizeof (*hs));
80       hs->thread_index = save_thread_index;
81     }
82 }
83
84 /** \brief Detach cache entry from session
85  */
86 static void
87 hss_detach_cache_entry (hss_session_t *hs)
88 {
89   hss_main_t *hsm = &hss_main;
90   hss_cache_entry_t *ep;
91
92   /*
93    * Decrement cache pool entry reference count
94    * Note that if e.g. a file lookup fails, the cache pool index
95    * won't be set
96    */
97   if (hs->cache_pool_index != ~0)
98     {
99       ep = pool_elt_at_index (hsm->cache_pool, hs->cache_pool_index);
100       ep->inuse--;
101       if (hsm->debug_level > 1)
102         clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
103                       ep->inuse);
104     }
105   hs->cache_pool_index = ~0;
106   if (hs->free_data)
107     vec_free (hs->data);
108   hs->data = 0;
109   hs->data_offset = 0;
110   hs->free_data = 0;
111   vec_free (hs->path);
112 }
113
114 /** \brief Disconnect a session
115  */
116 static void
117 hss_session_disconnect_transport (hss_session_t *hs)
118 {
119   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
120   a->handle = hs->vpp_session_handle;
121   a->app_index = hss_main.app_index;
122   vnet_disconnect_session (a);
123 }
124
125 /** \brief Sanity-check the forward and reverse LRU lists
126  */
127 static inline void
128 lru_validate (hss_main_t *hsm)
129 {
130 #if CLIB_DEBUG > 0
131   f64 last_timestamp;
132   u32 index;
133   int i;
134   hss_cache_entry_t *ep;
135
136   last_timestamp = 1e70;
137   for (i = 1, index = hsm->first_index; index != ~0;)
138     {
139       ep = pool_elt_at_index (hsm->cache_pool, index);
140       index = ep->next_index;
141       /* Timestamps should be smaller (older) as we walk the fwd list */
142       if (ep->last_used > last_timestamp)
143         {
144           clib_warning ("%d[%d]: last used %.6f, last_timestamp %.6f",
145                         ep - hsm->cache_pool, i,
146                         ep->last_used, last_timestamp);
147         }
148       last_timestamp = ep->last_used;
149       i++;
150     }
151
152   last_timestamp = 0.0;
153   for (i = 1, index = hsm->last_index; index != ~0;)
154     {
155       ep = pool_elt_at_index (hsm->cache_pool, index);
156       index = ep->prev_index;
157       /* Timestamps should be larger (newer) as we walk the rev list */
158       if (ep->last_used < last_timestamp)
159         {
160           clib_warning ("%d[%d]: last used %.6f, last_timestamp %.6f",
161                         ep - hsm->cache_pool, i,
162                         ep->last_used, last_timestamp);
163         }
164       last_timestamp = ep->last_used;
165       i++;
166     }
167 #endif
168 }
169
170 /** \brief Remove a data cache entry from the LRU lists
171  */
172 static inline void
173 lru_remove (hss_main_t *hsm, hss_cache_entry_t *ep)
174 {
175   hss_cache_entry_t *next_ep, *prev_ep;
176   u32 ep_index;
177
178   lru_validate (hsm);
179
180   ep_index = ep - hsm->cache_pool;
181
182   /* Deal with list heads */
183   if (ep_index == hsm->first_index)
184     hsm->first_index = ep->next_index;
185   if (ep_index == hsm->last_index)
186     hsm->last_index = ep->prev_index;
187
188   /* Fix next->prev */
189   if (ep->next_index != ~0)
190     {
191       next_ep = pool_elt_at_index (hsm->cache_pool, ep->next_index);
192       next_ep->prev_index = ep->prev_index;
193     }
194   /* Fix prev->next */
195   if (ep->prev_index != ~0)
196     {
197       prev_ep = pool_elt_at_index (hsm->cache_pool, ep->prev_index);
198       prev_ep->next_index = ep->next_index;
199     }
200   lru_validate (hsm);
201 }
202
203 /** \brief Add an entry to the LRU lists, tag w/ supplied timestamp
204  */
205 static inline void
206 lru_add (hss_main_t *hsm, hss_cache_entry_t *ep, f64 now)
207 {
208   hss_cache_entry_t *next_ep;
209   u32 ep_index;
210
211   lru_validate (hsm);
212
213   ep_index = ep - hsm->cache_pool;
214
215   /*
216    * Re-add at the head of the forward LRU list,
217    * tail of the reverse LRU list
218    */
219   if (hsm->first_index != ~0)
220     {
221       next_ep = pool_elt_at_index (hsm->cache_pool, hsm->first_index);
222       next_ep->prev_index = ep_index;
223     }
224
225   ep->prev_index = ~0;
226
227   /* ep now the new head of the LRU forward list */
228   ep->next_index = hsm->first_index;
229   hsm->first_index = ep_index;
230
231   /* single session case: also the tail of the reverse LRU list */
232   if (hsm->last_index == ~0)
233     hsm->last_index = ep_index;
234   ep->last_used = now;
235
236   lru_validate (hsm);
237 }
238
239 /** \brief Remove and re-add a cache entry from/to the LRU lists
240  */
241 static inline void
242 lru_update (hss_main_t *hsm, hss_cache_entry_t *ep, f64 now)
243 {
244   lru_remove (hsm, ep);
245   lru_add (hsm, ep, now);
246 }
247
248 static void
249 start_send_data (hss_session_t *hs, http_status_code_t status)
250 {
251   http_msg_t msg;
252   session_t *ts;
253   int rv;
254
255   ts = session_get (hs->vpp_session_index, hs->thread_index);
256
257   msg.type = HTTP_MSG_REPLY;
258   msg.code = status;
259   msg.content_type = HTTP_CONTENT_TEXT_HTML;
260   msg.data.len = hs->data_len;
261
262   if (hs->data_len > hss_main.use_ptr_thresh)
263     {
264       msg.data.type = HTTP_MSG_DATA_PTR;
265       rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
266       ASSERT (rv == sizeof (msg));
267
268       uword data = pointer_to_uword (hs->data);
269       rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (data), (u8 *) &data);
270       ASSERT (rv == sizeof (sizeof (data)));
271
272       goto done;
273     }
274
275   msg.data.type = HTTP_MSG_DATA_INLINE;
276
277   rv = svm_fifo_enqueue (ts->tx_fifo, sizeof (msg), (u8 *) &msg);
278   ASSERT (rv == sizeof (msg));
279
280   if (!msg.data.len)
281     goto done;
282
283   rv = svm_fifo_enqueue (ts->tx_fifo, hs->data_len, hs->data);
284
285   if (rv != hs->data_len)
286     {
287       hs->data_offset = rv;
288       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
289     }
290
291 done:
292
293   if (svm_fifo_set_event (ts->tx_fifo))
294     session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
295 }
296
297 __clib_export void
298 hss_session_send_data (hss_url_handler_args_t *args)
299 {
300   hss_session_t *hs;
301
302   hs = hss_session_get (args->sh.thread_index, args->sh.session_index);
303   hs->data = args->data;
304   hs->data_len = args->data_len;
305   hs->free_data = args->free_vec_data;
306   start_send_data (hs, args->sc);
307 }
308
309 static int
310 try_url_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
311                  u8 *request)
312 {
313   http_status_code_t sc = HTTP_STATUS_OK;
314   hss_url_handler_args_t args = {};
315   uword *p, *url_table;
316   int rv;
317
318   if (!hsm->enable_url_handlers)
319     return -1;
320
321   /* Look for built-in GET / POST handlers */
322   url_table =
323     (rt == HTTP_REQ_GET) ? hsm->get_url_handlers : hsm->post_url_handlers;
324
325   p = hash_get_mem (url_table, request);
326   if (!p)
327     return -1;
328
329   hs->path = 0;
330   hs->data_offset = 0;
331   hs->cache_pool_index = ~0;
332
333   if (hsm->debug_level > 0)
334     clib_warning ("%s '%s'", (rt == HTTP_REQ_GET) ? "GET" : "POST", request);
335
336   args.reqtype = rt;
337   args.request = request;
338   args.sh.thread_index = hs->thread_index;
339   args.sh.session_index = hs->session_index;
340
341   rv = ((hss_url_handler_fn) p[0]) (&args);
342
343   /* Wait for data from handler */
344   if (rv == HSS_URL_HANDLER_ASYNC)
345     return 0;
346
347   if (rv == HSS_URL_HANDLER_ERROR)
348     {
349       clib_warning ("builtin handler %llx hit on %s '%s' but failed!", p[0],
350                     (rt == HTTP_REQ_GET) ? "GET" : "POST", request);
351       sc = HTTP_STATUS_NOT_FOUND;
352     }
353
354   hs->data = args.data;
355   hs->data_len = args.data_len;
356   hs->free_data = args.free_vec_data;
357
358   start_send_data (hs, sc);
359
360   if (!hs->data)
361     hss_session_disconnect_transport (hs);
362
363   return 0;
364 }
365
366 static int
367 handle_request (hss_session_t *hs, http_req_method_t rt, u8 *request)
368 {
369   http_status_code_t sc = HTTP_STATUS_OK;
370   hss_main_t *hsm = &hss_main;
371   struct stat _sb, *sb = &_sb;
372   clib_error_t *error;
373   u8 *path;
374
375   if (!try_url_handler (hsm, hs, rt, request))
376     return 0;
377
378   /*
379    * Construct the file to open
380    * Browsers are capable of sporadically including a leading '/'
381    */
382   if (request[0] == '/')
383     path = format (0, "%s%s%c", hsm->www_root, request, 0);
384   else
385     path = format (0, "%s/%s%c", hsm->www_root, request, 0);
386
387   if (hsm->debug_level > 0)
388     clib_warning ("%s '%s'", (rt == HTTP_REQ_GET) ? "GET" : "POST", path);
389
390   /* Try to find the file. 2x special cases to find index.html */
391   if (stat ((char *) path, sb) < 0      /* cant even stat the file */
392       || sb->st_size < 20       /* file too small */
393       || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
394     {
395       u32 save_length = vec_len (path) - 1;
396       /* Try appending "index.html"... */
397       _vec_len (path) -= 1;
398       path = format (path, "index.html%c", 0);
399       if (stat ((char *) path, sb) < 0  /* cant even stat the file */
400           || sb->st_size < 20   /* file too small */
401           || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
402         {
403           _vec_len (path) = save_length;
404           path = format (path, "/index.html%c", 0);
405
406           /* Send a redirect, otherwise the browser will confuse itself */
407           if (stat ((char *) path, sb) < 0      /* cant even stat the file */
408               || sb->st_size < 20       /* file too small */
409               || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
410             {
411               sc = HTTP_STATUS_NOT_FOUND;
412               goto done;
413             }
414           else
415             {
416               transport_endpoint_t endpoint;
417               transport_proto_t proto;
418               u16 local_port;
419               int print_port = 0;
420               u8 *port_str = 0;
421               session_t *ts;
422
423               /*
424                * To make this bit work correctly, we need to know our local
425                * IP address, etc. and send it in the redirect...
426                */
427               u8 *redirect;
428
429               vec_delete (path, vec_len (hsm->www_root) - 1, 0);
430
431               ts = session_get (hs->vpp_session_index, hs->thread_index);
432               session_get_endpoint (ts, &endpoint, 1 /* is_local */);
433
434               local_port = clib_net_to_host_u16 (endpoint.port);
435
436               proto = session_type_transport_proto (ts->session_type);
437
438               if ((proto == TRANSPORT_PROTO_TCP && local_port != 80)
439                   || (proto == TRANSPORT_PROTO_TLS && local_port != 443))
440                 {
441                   print_port = 1;
442                   port_str = format (0, ":%u", (u32) local_port);
443                 }
444
445               redirect = format (0, "HTTP/1.1 301 Moved Permanently\r\n"
446                                  "Location: http%s://%U%s%s\r\n\r\n",
447                                  proto == TRANSPORT_PROTO_TLS ? "s" : "",
448                                  format_ip46_address, &endpoint.ip,
449                                  endpoint.is_ip4,
450                                  print_port ? port_str : (u8 *) "", path);
451               if (hsm->debug_level > 0)
452                 clib_warning ("redirect: %s", redirect);
453
454               vec_free (port_str);
455
456               hs->data = redirect;
457               goto done;
458             }
459         }
460     }
461
462   /* find or read the file if we haven't done so yet. */
463   if (hs->data == 0)
464     {
465       BVT (clib_bihash_kv) kv;
466       hss_cache_entry_t *ce;
467
468       hs->path = path;
469
470       /* First, try the cache */
471       kv.key = (u64) hs->path;
472       if (BV (clib_bihash_search) (&hsm->name_to_data, &kv, &kv) == 0)
473         {
474           if (hsm->debug_level > 1)
475             clib_warning ("lookup '%s' returned %lld", kv.key, kv.value);
476
477           hss_cache_lock ();
478
479           /* found the data.. */
480           ce = pool_elt_at_index (hsm->cache_pool, kv.value);
481           hs->data = ce->data;
482           /* Update the cache entry, mark it in-use */
483           lru_update (hsm, ce, vlib_time_now (vlib_get_main ()));
484           hs->cache_pool_index = ce - hsm->cache_pool;
485           ce->inuse++;
486           if (hsm->debug_level > 1)
487             clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
488                           ce->inuse);
489
490           hss_cache_unlock ();
491         }
492       else
493         {
494           hss_cache_lock ();
495
496           if (hsm->debug_level > 1)
497             clib_warning ("lookup '%s' failed", kv.key, kv.value);
498           /* Need to recycle one (or more cache) entries? */
499           if (hsm->cache_size > hsm->cache_limit)
500             {
501               int free_index = hsm->last_index;
502
503               while (free_index != ~0)
504                 {
505                   /* pick the LRU */
506                   ce = pool_elt_at_index (hsm->cache_pool, free_index);
507                   free_index = ce->prev_index;
508                   /* Which could be in use... */
509                   if (ce->inuse)
510                     {
511                       if (hsm->debug_level > 1)
512                         clib_warning ("index %d in use refcnt %d",
513                                       ce - hsm->cache_pool, ce->inuse);
514                     }
515                   kv.key = (u64) (ce->filename);
516                   kv.value = ~0ULL;
517                   if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
518                                                 0 /* is_add */ ) < 0)
519                     {
520                       clib_warning ("LRU delete '%s' FAILED!", ce->filename);
521                     }
522                   else if (hsm->debug_level > 1)
523                     clib_warning ("LRU delete '%s' ok", ce->filename);
524
525                   lru_remove (hsm, ce);
526                   hsm->cache_size -= vec_len (ce->data);
527                   hsm->cache_evictions++;
528                   vec_free (ce->filename);
529                   vec_free (ce->data);
530                   if (hsm->debug_level > 1)
531                     clib_warning ("pool put index %d", ce - hsm->cache_pool);
532                   pool_put (hsm->cache_pool, ce);
533                   if (hsm->cache_size < hsm->cache_limit)
534                     break;
535                 }
536             }
537
538           /* Read the file */
539           error = clib_file_contents ((char *) (hs->path), &hs->data);
540           if (error)
541             {
542               clib_warning ("Error reading '%s'", hs->path);
543               clib_error_report (error);
544               sc = HTTP_STATUS_INTERNAL_ERROR;
545               hss_cache_unlock ();
546               goto done;
547             }
548           /* Create a cache entry for it */
549           pool_get_zero (hsm->cache_pool, ce);
550           ce->filename = vec_dup (hs->path);
551           ce->data = hs->data;
552           hs->cache_pool_index = ce - hsm->cache_pool;
553           ce->inuse++;
554           if (hsm->debug_level > 1)
555             clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
556                           ce->inuse);
557           lru_add (hsm, ce, vlib_time_now (vlib_get_main ()));
558           kv.key = (u64) vec_dup (hs->path);
559           kv.value = ce - hsm->cache_pool;
560           /* Add to the lookup table */
561           if (hsm->debug_level > 1)
562             clib_warning ("add '%s' value %lld", kv.key, kv.value);
563
564           if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
565                                         1 /* is_add */ ) < 0)
566             {
567               clib_warning ("BUG: add failed!");
568             }
569           hsm->cache_size += vec_len (ce->data);
570
571           hss_cache_unlock ();
572         }
573       hs->data_offset = 0;
574     }
575
576 done:
577
578   start_send_data (hs, sc);
579   if (!hs->data)
580     hss_session_disconnect_transport (hs);
581
582   return sc;
583 }
584
585 static int
586 hss_ts_rx_callback (session_t *ts)
587 {
588   hss_session_t *hs;
589   u8 *request = 0;
590   http_msg_t msg;
591   int rv;
592
593   hs = hss_session_get (ts->thread_index, ts->opaque);
594
595   /* Read the http message header */
596   rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
597   ASSERT (rv == sizeof (msg));
598
599   if (msg.type != HTTP_MSG_REQUEST ||
600       (msg.method_type != HTTP_REQ_GET && msg.method_type != HTTP_REQ_POST))
601     {
602       hs->data = 0;
603       start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED);
604       return 0;
605     }
606
607   /* Read request */
608   vec_validate (request, msg.data.len - 1);
609   rv = svm_fifo_dequeue (ts->rx_fifo, msg.data.len, request);
610   ASSERT (rv == msg.data.len);
611
612   /* Find and send data */
613   handle_request (hs, msg.method_type, request);
614
615   vec_free (request);
616
617   return 0;
618 }
619
620 static int
621 hss_ts_tx_callback (session_t *ts)
622 {
623   hss_session_t *hs;
624   u32 to_send;
625   int rv;
626
627   hs = hss_session_get (ts->thread_index, ts->opaque);
628   if (!hs || !hs->data)
629     return 0;
630
631   to_send = hs->data_len - hs->data_offset;
632   rv = svm_fifo_enqueue (ts->tx_fifo, to_send, hs->data + hs->data_offset);
633
634   if (rv <= 0)
635     {
636       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
637       return 0;
638     }
639
640   if (rv < to_send)
641     {
642       hs->data_offset += rv;
643       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
644     }
645
646   if (svm_fifo_set_event (ts->tx_fifo))
647     session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
648
649   return 0;
650 }
651
652 /** \brief Session accept callback
653  */
654 static int
655 hss_ts_accept_callback (session_t *ts)
656 {
657   hss_session_t *hs;
658   u32 thresh;
659
660   hs = hss_session_alloc (ts->thread_index);
661
662   hs->vpp_session_index = ts->session_index;
663   hs->vpp_session_handle = session_handle (ts);
664
665   /* The application sets a threshold for it's fifo to get notified when
666    * additional data can be enqueued. We want to keep the TX fifo reasonably
667    * full, however avoid entering a state where the
668    * fifo is full all the time and small chunks of data are being enqueued
669    * each time. If the fifo is small (under 16K) we set
670    * the threshold to it's size, meaning a notification will be given when the
671    * fifo empties.
672    */
673   thresh = clib_min (svm_fifo_size (ts->tx_fifo), HSS_FIFO_THRESH);
674   svm_fifo_set_deq_thresh (ts->tx_fifo, thresh);
675
676   ts->opaque = hs->session_index;
677   ts->session_state = SESSION_STATE_READY;
678   return 0;
679 }
680
681 static void
682 hss_ts_disconnect_callback (session_t *ts)
683 {
684   hss_main_t *hsm = &hss_main;
685   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
686
687   a->handle = session_handle (ts);
688   a->app_index = hsm->app_index;
689   vnet_disconnect_session (a);
690 }
691
692 static void
693 hss_ts_reset_callback (session_t *ts)
694 {
695   hss_main_t *hsm = &hss_main;
696   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
697
698   a->handle = session_handle (ts);
699   a->app_index = hsm->app_index;
700   vnet_disconnect_session (a);
701 }
702
703 static int
704 hss_ts_connected_callback (u32 app_index, u32 api_context, session_t *ts,
705                            session_error_t err)
706 {
707   clib_warning ("called...");
708   return -1;
709 }
710
711 static int
712 hss_add_segment_callback (u32 client_index, u64 segment_handle)
713 {
714   return 0;
715 }
716
717 static void
718 hss_ts_cleanup (session_t *s, session_cleanup_ntf_t ntf)
719 {
720   hss_session_t *hs;
721
722   if (ntf == SESSION_CLEANUP_TRANSPORT)
723     return;
724
725   hs = hss_session_get (s->thread_index, s->opaque);
726   if (!hs)
727     return;
728
729   hss_detach_cache_entry (hs);
730   hss_session_free (hs);
731 }
732
733 static session_cb_vft_t hss_cb_vft = {
734   .session_accept_callback = hss_ts_accept_callback,
735   .session_disconnect_callback = hss_ts_disconnect_callback,
736   .session_connected_callback = hss_ts_connected_callback,
737   .add_segment_callback = hss_add_segment_callback,
738   .builtin_app_rx_callback = hss_ts_rx_callback,
739   .builtin_app_tx_callback = hss_ts_tx_callback,
740   .session_reset_callback = hss_ts_reset_callback,
741   .session_cleanup_callback = hss_ts_cleanup,
742 };
743
744 static int
745 hss_attach ()
746 {
747   vnet_app_add_cert_key_pair_args_t _ck_pair, *ck_pair = &_ck_pair;
748   hss_main_t *hsm = &hss_main;
749   u64 options[APP_OPTIONS_N_OPTIONS];
750   vnet_app_attach_args_t _a, *a = &_a;
751   u32 segment_size = 128 << 20;
752
753   clib_memset (a, 0, sizeof (*a));
754   clib_memset (options, 0, sizeof (options));
755
756   if (hsm->private_segment_size)
757     segment_size = hsm->private_segment_size;
758
759   a->api_client_index = ~0;
760   a->name = format (0, "http_static_server");
761   a->session_cb_vft = &hss_cb_vft;
762   a->options = options;
763   a->options[APP_OPTIONS_SEGMENT_SIZE] = segment_size;
764   a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = segment_size;
765   a->options[APP_OPTIONS_RX_FIFO_SIZE] =
766     hsm->fifo_size ? hsm->fifo_size : 8 << 10;
767   a->options[APP_OPTIONS_TX_FIFO_SIZE] =
768     hsm->fifo_size ? hsm->fifo_size : 32 << 10;
769   a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
770   a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = hsm->prealloc_fifos;
771   a->options[APP_OPTIONS_TLS_ENGINE] = CRYPTO_ENGINE_OPENSSL;
772
773   if (vnet_application_attach (a))
774     {
775       vec_free (a->name);
776       clib_warning ("failed to attach server");
777       return -1;
778     }
779   vec_free (a->name);
780   hsm->app_index = a->app_index;
781
782   clib_memset (ck_pair, 0, sizeof (*ck_pair));
783   ck_pair->cert = (u8 *) test_srv_crt_rsa;
784   ck_pair->key = (u8 *) test_srv_key_rsa;
785   ck_pair->cert_len = test_srv_crt_rsa_len;
786   ck_pair->key_len = test_srv_key_rsa_len;
787   vnet_app_add_cert_key_pair (ck_pair);
788   hsm->ckpair_index = ck_pair->index;
789
790   return 0;
791 }
792
793 static int
794 hss_transport_needs_crypto (transport_proto_t proto)
795 {
796   return proto == TRANSPORT_PROTO_TLS || proto == TRANSPORT_PROTO_DTLS ||
797          proto == TRANSPORT_PROTO_QUIC;
798 }
799
800 static int
801 hss_listen (void)
802 {
803   hss_main_t *hsm = &hss_main;
804   session_endpoint_cfg_t sep = SESSION_ENDPOINT_CFG_NULL;
805   vnet_listen_args_t _a, *a = &_a;
806   char *uri = "tcp://0.0.0.0/80";
807   u8 need_crypto;
808   int rv;
809
810   clib_memset (a, 0, sizeof (*a));
811   a->app_index = hsm->app_index;
812
813   if (hsm->uri)
814     uri = (char *) hsm->uri;
815
816   if (parse_uri (uri, &sep))
817     return -1;
818
819   need_crypto = hss_transport_needs_crypto (a->sep_ext.transport_proto);
820
821   sep.transport_proto = TRANSPORT_PROTO_HTTP;
822   clib_memcpy (&a->sep_ext, &sep, sizeof (sep));
823
824   if (need_crypto)
825     {
826       session_endpoint_alloc_ext_cfg (&a->sep_ext,
827                                       TRANSPORT_ENDPT_EXT_CFG_CRYPTO);
828       a->sep_ext.ext_cfg->crypto.ckpair_index = hsm->ckpair_index;
829     }
830
831   rv = vnet_listen (a);
832
833   if (need_crypto)
834     clib_mem_free (a->sep_ext.ext_cfg);
835
836   return rv;
837 }
838
839 static void
840 hss_url_handlers_init (hss_main_t *hsm)
841 {
842   if (!hsm->get_url_handlers)
843     {
844       hsm->get_url_handlers = hash_create_string (0, sizeof (uword));
845       hsm->post_url_handlers = hash_create_string (0, sizeof (uword));
846     }
847
848   hss_builtinurl_json_handlers_init ();
849 }
850
851 int
852 hss_create (vlib_main_t *vm)
853 {
854   vlib_thread_main_t *vtm = vlib_get_thread_main ();
855   hss_main_t *hsm = &hss_main;
856   u32 num_threads;
857
858   num_threads = 1 /* main thread */  + vtm->n_threads;
859   vec_validate (hsm->sessions, num_threads - 1);
860
861   clib_spinlock_init (&hsm->cache_lock);
862
863   if (hss_attach ())
864     {
865       clib_warning ("failed to attach server");
866       return -1;
867     }
868   if (hss_listen ())
869     {
870       clib_warning ("failed to start listening");
871       return -1;
872     }
873
874   /* Init path-to-cache hash table */
875   BV (clib_bihash_init) (&hsm->name_to_data, "http cache", 128, 32 << 20);
876
877   if (hsm->enable_url_handlers)
878     hss_url_handlers_init (hsm);
879
880   return 0;
881 }
882
883 static clib_error_t *
884 hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
885                        vlib_cli_command_t *cmd)
886 {
887   unformat_input_t _line_input, *line_input = &_line_input;
888   hss_main_t *hsm = &hss_main;
889   clib_error_t *error = 0;
890   u64 seg_size;
891   int rv;
892
893   if (hsm->app_index != (u32) ~0)
894     return clib_error_return (0, "http server already running...");
895
896   hsm->prealloc_fifos = 0;
897   hsm->private_segment_size = 0;
898   hsm->fifo_size = 0;
899   /* 10mb cache limit, before LRU occurs */
900   hsm->cache_limit = 10 << 20;
901
902   /* Get a line of input. */
903   if (!unformat_user (input, unformat_line_input, line_input))
904     goto no_input;
905
906   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
907     {
908       if (unformat (line_input, "www-root %s", &hsm->www_root))
909         ;
910       else
911         if (unformat (line_input, "prealloc-fifos %d", &hsm->prealloc_fifos))
912         ;
913       else if (unformat (line_input, "private-segment-size %U",
914                          unformat_memory_size, &seg_size))
915         hsm->private_segment_size = seg_size;
916       else if (unformat (line_input, "fifo-size %d", &hsm->fifo_size))
917         hsm->fifo_size <<= 10;
918       else if (unformat (line_input, "cache-size %U", unformat_memory_size,
919                          &hsm->cache_limit))
920         ;
921       else if (unformat (line_input, "uri %s", &hsm->uri))
922         ;
923       else if (unformat (line_input, "debug %d", &hsm->debug_level))
924         ;
925       else if (unformat (line_input, "debug"))
926         hsm->debug_level = 1;
927       else if (unformat (line_input, "ptr-thresh %U", unformat_memory_size,
928                          &hsm->use_ptr_thresh))
929         ;
930       else if (unformat (line_input, "url-handlers"))
931         hsm->enable_url_handlers = 1;
932       else
933         {
934           error = clib_error_return (0, "unknown input `%U'",
935                                      format_unformat_error, line_input);
936         }
937     }
938
939   unformat_free (line_input);
940
941 no_input:
942
943   if (error)
944     goto done;
945
946   if (hsm->www_root == 0 && !hsm->enable_url_handlers)
947     {
948       error = clib_error_return (0, "Must set www-root or url-handlers");
949       goto done;
950     }
951
952   if (hsm->cache_limit < (128 << 10))
953     {
954       error = clib_error_return (0, "cache-size must be at least 128kb");
955       vec_free (hsm->www_root);
956       goto done;
957     }
958
959   vnet_session_enable_disable (vm, 1 /* turn on TCP, etc. */ );
960
961   if ((rv = hss_create (vm)))
962     {
963       error = clib_error_return (0, "server_create returned %d", rv);
964       vec_free (hsm->www_root);
965     }
966
967 done:
968
969   return error;
970 }
971
972 /*?
973  * Enable the static http server
974  *
975  * @cliexpar
976  * This command enables the static http server. Only the www-root
977  * parameter is required
978  * @clistart
979  * http static server www-root /tmp/www uri tcp://0.0.0.0/80 cache-size 2m
980  * @cliend
981  * @cliexcmd{http static server www-root <path> [prealloc-fios <nn>]
982  *   [private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]}
983 ?*/
984 VLIB_CLI_COMMAND (hss_create_command, static) = {
985   .path = "http static server",
986   .short_help =
987     "http static server www-root <path> [prealloc-fifos <nn>]\n"
988     "[private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]\n"
989     "[ptr-thresh <nn>] [url-handlers] [debug [nn]]\n",
990   .function = hss_create_command_fn,
991 };
992
993 /** \brief format a file cache entry
994  */
995 static u8 *
996 format_hss_cache_entry (u8 *s, va_list *args)
997 {
998   hss_cache_entry_t *ep = va_arg (*args, hss_cache_entry_t *);
999   f64 now = va_arg (*args, f64);
1000
1001   /* Header */
1002   if (ep == 0)
1003     {
1004       s = format (s, "%40s%12s%20s", "File", "Size", "Age");
1005       return s;
1006     }
1007   s = format (s, "%40s%12lld%20.2f", ep->filename, vec_len (ep->data),
1008               now - ep->last_used);
1009   return s;
1010 }
1011
1012 static u8 *
1013 format_hss_session (u8 *s, va_list *args)
1014 {
1015   hss_session_t *hs = va_arg (*args, hss_session_t *);
1016   int __clib_unused verbose = va_arg (*args, int);
1017
1018   s = format (s, "\n path %s, data length %u, data_offset %u",
1019               hs->path ? hs->path : (u8 *) "[none]", hs->data_len,
1020               hs->data_offset);
1021   return s;
1022 }
1023
1024 static clib_error_t *
1025 hss_show_command_fn (vlib_main_t *vm, unformat_input_t *input,
1026                      vlib_cli_command_t *cmd)
1027 {
1028   hss_main_t *hsm = &hss_main;
1029   hss_cache_entry_t *ep, **entries = 0;
1030   int verbose = 0;
1031   int show_cache = 0;
1032   int show_sessions = 0;
1033   u32 index;
1034   f64 now;
1035
1036   if (hsm->www_root == 0)
1037     return clib_error_return (0, "Static server disabled");
1038
1039   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
1040     {
1041       if (unformat (input, "verbose %d", &verbose))
1042         ;
1043       else if (unformat (input, "verbose"))
1044         verbose = 1;
1045       else if (unformat (input, "cache"))
1046         show_cache = 1;
1047       else if (unformat (input, "sessions"))
1048         show_sessions = 1;
1049       else
1050         break;
1051     }
1052
1053   if ((show_cache + show_sessions) == 0)
1054     return clib_error_return (0, "specify one or more of cache, sessions");
1055
1056   if (show_cache)
1057     {
1058       if (verbose == 0)
1059         {
1060           vlib_cli_output
1061             (vm, "www_root %s, cache size %lld bytes, limit %lld bytes, "
1062              "evictions %lld",
1063              hsm->www_root, hsm->cache_size, hsm->cache_limit,
1064              hsm->cache_evictions);
1065           return 0;
1066         }
1067
1068       now = vlib_time_now (vm);
1069
1070       vlib_cli_output (vm, "%U", format_hss_cache_entry, 0 /* header */, now);
1071
1072       for (index = hsm->first_index; index != ~0;)
1073         {
1074           ep = pool_elt_at_index (hsm->cache_pool, index);
1075           index = ep->next_index;
1076           vlib_cli_output (vm, "%U", format_hss_cache_entry, ep, now);
1077         }
1078
1079       vlib_cli_output (vm, "%40s%12lld", "Total Size", hsm->cache_size);
1080
1081       vec_free (entries);
1082     }
1083
1084   if (show_sessions)
1085     {
1086       u32 *session_indices = 0;
1087       hss_session_t *hs;
1088       int i, j;
1089
1090       hss_cache_lock ();
1091
1092       for (i = 0; i < vec_len (hsm->sessions); i++)
1093         {
1094           pool_foreach (hs, hsm->sessions[i])
1095            {
1096             vec_add1 (session_indices, hs - hsm->sessions[i]);
1097           }
1098
1099           for (j = 0; j < vec_len (session_indices); j++)
1100             {
1101               vlib_cli_output (
1102                 vm, "%U", format_hss_session,
1103                 pool_elt_at_index (hsm->sessions[i], session_indices[j]),
1104                 verbose);
1105             }
1106           vec_reset_length (session_indices);
1107         }
1108       hss_cache_unlock ();
1109       vec_free (session_indices);
1110     }
1111   return 0;
1112 }
1113
1114 /*?
1115  * Display static http server cache statistics
1116  *
1117  * @cliexpar
1118  * This command shows the contents of the static http server cache
1119  * @clistart
1120  * show http static server
1121  * @cliend
1122  * @cliexcmd{show http static server sessions cache [verbose [nn]]}
1123 ?*/
1124 VLIB_CLI_COMMAND (hss_show_command, static) = {
1125   .path = "show http static server",
1126   .short_help = "show http static server sessions cache [verbose [<nn>]]",
1127   .function = hss_show_command_fn,
1128 };
1129
1130 static clib_error_t *
1131 hss_clear_cache_command_fn (vlib_main_t *vm, unformat_input_t *input,
1132                             vlib_cli_command_t *cmd)
1133 {
1134   hss_main_t *hsm = &hss_main;
1135   hss_cache_entry_t *ce;
1136   u32 free_index;
1137   u32 busy_items = 0;
1138   BVT (clib_bihash_kv) kv;
1139
1140   if (hsm->www_root == 0)
1141     return clib_error_return (0, "Static server disabled");
1142
1143   hss_cache_lock ();
1144
1145   /* Walk the LRU list to find active entries */
1146   free_index = hsm->last_index;
1147   while (free_index != ~0)
1148     {
1149       ce = pool_elt_at_index (hsm->cache_pool, free_index);
1150       free_index = ce->prev_index;
1151       /* Which could be in use... */
1152       if (ce->inuse)
1153         {
1154           busy_items++;
1155           free_index = ce->next_index;
1156           continue;
1157         }
1158       kv.key = (u64) (ce->filename);
1159       kv.value = ~0ULL;
1160       if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
1161                                     0 /* is_add */ ) < 0)
1162         {
1163           clib_warning ("BUG: cache clear delete '%s' FAILED!", ce->filename);
1164         }
1165
1166       lru_remove (hsm, ce);
1167       hsm->cache_size -= vec_len (ce->data);
1168       hsm->cache_evictions++;
1169       vec_free (ce->filename);
1170       vec_free (ce->data);
1171       if (hsm->debug_level > 1)
1172         clib_warning ("pool put index %d", ce - hsm->cache_pool);
1173       pool_put (hsm->cache_pool, ce);
1174       free_index = hsm->last_index;
1175     }
1176   hss_cache_unlock ();
1177   if (busy_items > 0)
1178     vlib_cli_output (vm, "Note: %d busy items still in cache...", busy_items);
1179   else
1180     vlib_cli_output (vm, "Cache cleared...");
1181   return 0;
1182 }
1183
1184 /*?
1185  * Clear the static http server cache, to force the server to
1186  * reload content from backing files
1187  *
1188  * @cliexpar
1189  * This command clear the static http server cache
1190  * @clistart
1191  * clear http static cache
1192  * @cliend
1193  * @cliexcmd{clear http static cache}
1194 ?*/
1195 VLIB_CLI_COMMAND (clear_hss_cache_command, static) = {
1196   .path = "clear http static cache",
1197   .short_help = "clear http static cache",
1198   .function = hss_clear_cache_command_fn,
1199 };
1200
1201 static clib_error_t *
1202 hss_main_init (vlib_main_t *vm)
1203 {
1204   hss_main_t *hsm = &hss_main;
1205
1206   hsm->app_index = ~0;
1207   hsm->vlib_main = vm;
1208   hsm->first_index = hsm->last_index = ~0;
1209
1210   return 0;
1211 }
1212
1213 VLIB_INIT_FUNCTION (hss_main_init);
1214
1215 /*
1216  * fd.io coding-style-patch-verification: ON
1217  *
1218  * Local Variables:
1219  * eval: (c-set-style "gnu")
1220  * End:
1221  */