http_static: fix coverity warning
[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
304   if (hs->data && hs->free_data)
305     vec_free (hs->data);
306
307   hs->data = args->data;
308   hs->data_len = args->data_len;
309   hs->free_data = args->free_vec_data;
310   start_send_data (hs, args->sc);
311 }
312
313 static int
314 try_url_handler (hss_main_t *hsm, hss_session_t *hs, http_req_method_t rt,
315                  u8 *request)
316 {
317   http_status_code_t sc = HTTP_STATUS_OK;
318   hss_url_handler_args_t args = {};
319   uword *p, *url_table;
320   int rv;
321
322   if (!hsm->enable_url_handlers || !request)
323     return -1;
324
325   /* Look for built-in GET / POST handlers */
326   url_table =
327     (rt == HTTP_REQ_GET) ? hsm->get_url_handlers : hsm->post_url_handlers;
328
329   p = hash_get_mem (url_table, request);
330   if (!p)
331     return -1;
332
333   hs->path = 0;
334   hs->data_offset = 0;
335   hs->cache_pool_index = ~0;
336
337   if (hsm->debug_level > 0)
338     clib_warning ("%s '%s'", (rt == HTTP_REQ_GET) ? "GET" : "POST", request);
339
340   args.reqtype = rt;
341   args.request = request;
342   args.sh.thread_index = hs->thread_index;
343   args.sh.session_index = hs->session_index;
344
345   rv = ((hss_url_handler_fn) p[0]) (&args);
346
347   /* Wait for data from handler */
348   if (rv == HSS_URL_HANDLER_ASYNC)
349     return 0;
350
351   if (rv == HSS_URL_HANDLER_ERROR)
352     {
353       clib_warning ("builtin handler %llx hit on %s '%s' but failed!", p[0],
354                     (rt == HTTP_REQ_GET) ? "GET" : "POST", request);
355       sc = HTTP_STATUS_NOT_FOUND;
356     }
357
358   hs->data = args.data;
359   hs->data_len = args.data_len;
360   hs->free_data = args.free_vec_data;
361
362   start_send_data (hs, sc);
363
364   if (!hs->data)
365     hss_session_disconnect_transport (hs);
366
367   return 0;
368 }
369
370 static int
371 handle_request (hss_session_t *hs, http_req_method_t rt, u8 *request)
372 {
373   http_status_code_t sc = HTTP_STATUS_OK;
374   hss_main_t *hsm = &hss_main;
375   struct stat _sb, *sb = &_sb;
376   clib_error_t *error;
377   u8 *path;
378
379   if (!try_url_handler (hsm, hs, rt, request))
380     return 0;
381
382   if (!hsm->www_root)
383     {
384       sc = HTTP_STATUS_NOT_FOUND;
385       goto done;
386     }
387
388   /*
389    * Construct the file to open
390    * Browsers are capable of sporadically including a leading '/'
391    */
392   if (!request)
393     path = format (0, "%s%c", hsm->www_root, 0);
394   else if (request[0] == '/')
395     path = format (0, "%s%s%c", hsm->www_root, request, 0);
396   else
397     path = format (0, "%s/%s%c", hsm->www_root, request, 0);
398
399   if (hsm->debug_level > 0)
400     clib_warning ("%s '%s'", (rt == HTTP_REQ_GET) ? "GET" : "POST", path);
401
402   /* Try to find the file. 2x special cases to find index.html */
403   if (stat ((char *) path, sb) < 0      /* cant even stat the file */
404       || sb->st_size < 20       /* file too small */
405       || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
406     {
407       u32 save_length = vec_len (path) - 1;
408       /* Try appending "index.html"... */
409       _vec_len (path) -= 1;
410       path = format (path, "index.html%c", 0);
411       if (stat ((char *) path, sb) < 0  /* cant even stat the file */
412           || sb->st_size < 20   /* file too small */
413           || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
414         {
415           _vec_len (path) = save_length;
416           path = format (path, "/index.html%c", 0);
417
418           /* Send a redirect, otherwise the browser will confuse itself */
419           if (stat ((char *) path, sb) < 0      /* cant even stat the file */
420               || sb->st_size < 20       /* file too small */
421               || (sb->st_mode & S_IFMT) != S_IFREG /* not a regular file */ )
422             {
423               sc = HTTP_STATUS_NOT_FOUND;
424               goto done;
425             }
426           else
427             {
428               transport_endpoint_t endpoint;
429               transport_proto_t proto;
430               u16 local_port;
431               int print_port = 0;
432               u8 *port_str = 0;
433               session_t *ts;
434
435               /*
436                * To make this bit work correctly, we need to know our local
437                * IP address, etc. and send it in the redirect...
438                */
439               u8 *redirect;
440
441               vec_delete (path, vec_len (hsm->www_root) - 1, 0);
442
443               ts = session_get (hs->vpp_session_index, hs->thread_index);
444               session_get_endpoint (ts, &endpoint, 1 /* is_local */);
445
446               local_port = clib_net_to_host_u16 (endpoint.port);
447
448               proto = session_type_transport_proto (ts->session_type);
449
450               if ((proto == TRANSPORT_PROTO_TCP && local_port != 80)
451                   || (proto == TRANSPORT_PROTO_TLS && local_port != 443))
452                 {
453                   print_port = 1;
454                   port_str = format (0, ":%u", (u32) local_port);
455                 }
456
457               redirect = format (0, "HTTP/1.1 301 Moved Permanently\r\n"
458                                  "Location: http%s://%U%s%s\r\n\r\n",
459                                  proto == TRANSPORT_PROTO_TLS ? "s" : "",
460                                  format_ip46_address, &endpoint.ip,
461                                  endpoint.is_ip4,
462                                  print_port ? port_str : (u8 *) "", path);
463               if (hsm->debug_level > 0)
464                 clib_warning ("redirect: %s", redirect);
465
466               vec_free (port_str);
467
468               hs->data = redirect;
469               goto done;
470             }
471         }
472     }
473
474   /* find or read the file if we haven't done so yet. */
475   if (hs->data == 0)
476     {
477       BVT (clib_bihash_kv) kv;
478       hss_cache_entry_t *ce;
479
480       hs->path = path;
481
482       /* First, try the cache */
483       kv.key = (u64) hs->path;
484       if (BV (clib_bihash_search) (&hsm->name_to_data, &kv, &kv) == 0)
485         {
486           if (hsm->debug_level > 1)
487             clib_warning ("lookup '%s' returned %lld", kv.key, kv.value);
488
489           hss_cache_lock ();
490
491           /* found the data.. */
492           ce = pool_elt_at_index (hsm->cache_pool, kv.value);
493           hs->data = ce->data;
494           /* Update the cache entry, mark it in-use */
495           lru_update (hsm, ce, vlib_time_now (vlib_get_main ()));
496           hs->cache_pool_index = ce - hsm->cache_pool;
497           ce->inuse++;
498           if (hsm->debug_level > 1)
499             clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
500                           ce->inuse);
501
502           hss_cache_unlock ();
503         }
504       else
505         {
506           hss_cache_lock ();
507
508           if (hsm->debug_level > 1)
509             clib_warning ("lookup '%s' failed", kv.key, kv.value);
510           /* Need to recycle one (or more cache) entries? */
511           if (hsm->cache_size > hsm->cache_limit)
512             {
513               int free_index = hsm->last_index;
514
515               while (free_index != ~0)
516                 {
517                   /* pick the LRU */
518                   ce = pool_elt_at_index (hsm->cache_pool, free_index);
519                   free_index = ce->prev_index;
520                   /* Which could be in use... */
521                   if (ce->inuse)
522                     {
523                       if (hsm->debug_level > 1)
524                         clib_warning ("index %d in use refcnt %d",
525                                       ce - hsm->cache_pool, ce->inuse);
526                     }
527                   kv.key = (u64) (ce->filename);
528                   kv.value = ~0ULL;
529                   if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
530                                                 0 /* is_add */ ) < 0)
531                     {
532                       clib_warning ("LRU delete '%s' FAILED!", ce->filename);
533                     }
534                   else if (hsm->debug_level > 1)
535                     clib_warning ("LRU delete '%s' ok", ce->filename);
536
537                   lru_remove (hsm, ce);
538                   hsm->cache_size -= vec_len (ce->data);
539                   hsm->cache_evictions++;
540                   vec_free (ce->filename);
541                   vec_free (ce->data);
542                   if (hsm->debug_level > 1)
543                     clib_warning ("pool put index %d", ce - hsm->cache_pool);
544                   pool_put (hsm->cache_pool, ce);
545                   if (hsm->cache_size < hsm->cache_limit)
546                     break;
547                 }
548             }
549
550           /* Read the file */
551           error = clib_file_contents ((char *) (hs->path), &hs->data);
552           if (error)
553             {
554               clib_warning ("Error reading '%s'", hs->path);
555               clib_error_report (error);
556               sc = HTTP_STATUS_INTERNAL_ERROR;
557               hss_cache_unlock ();
558               goto done;
559             }
560           /* Create a cache entry for it */
561           pool_get_zero (hsm->cache_pool, ce);
562           ce->filename = vec_dup (hs->path);
563           ce->data = hs->data;
564           hs->cache_pool_index = ce - hsm->cache_pool;
565           ce->inuse++;
566           if (hsm->debug_level > 1)
567             clib_warning ("index %d refcnt now %d", hs->cache_pool_index,
568                           ce->inuse);
569           lru_add (hsm, ce, vlib_time_now (vlib_get_main ()));
570           kv.key = (u64) vec_dup (hs->path);
571           kv.value = ce - hsm->cache_pool;
572           /* Add to the lookup table */
573           if (hsm->debug_level > 1)
574             clib_warning ("add '%s' value %lld", kv.key, kv.value);
575
576           if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
577                                         1 /* is_add */ ) < 0)
578             {
579               clib_warning ("BUG: add failed!");
580             }
581           hsm->cache_size += vec_len (ce->data);
582
583           hss_cache_unlock ();
584         }
585       hs->data_offset = 0;
586     }
587
588 done:
589
590   start_send_data (hs, sc);
591   if (!hs->data)
592     hss_session_disconnect_transport (hs);
593
594   return sc;
595 }
596
597 static int
598 hss_ts_rx_callback (session_t *ts)
599 {
600   hss_session_t *hs;
601   u8 *request = 0;
602   http_msg_t msg;
603   int rv;
604
605   hs = hss_session_get (ts->thread_index, ts->opaque);
606
607   /* Read the http message header */
608   rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
609   ASSERT (rv == sizeof (msg));
610
611   if (msg.type != HTTP_MSG_REQUEST ||
612       (msg.method_type != HTTP_REQ_GET && msg.method_type != HTTP_REQ_POST))
613     {
614       hs->data = 0;
615       start_send_data (hs, HTTP_STATUS_METHOD_NOT_ALLOWED);
616       return 0;
617     }
618
619   /* Read request */
620   if (msg.data.len)
621     {
622       vec_validate (request, msg.data.len - 1);
623       rv = svm_fifo_dequeue (ts->rx_fifo, msg.data.len, request);
624       ASSERT (rv == msg.data.len);
625     }
626
627   /* Find and send data */
628   handle_request (hs, msg.method_type, request);
629
630   vec_free (request);
631
632   return 0;
633 }
634
635 static int
636 hss_ts_tx_callback (session_t *ts)
637 {
638   hss_session_t *hs;
639   u32 to_send;
640   int rv;
641
642   hs = hss_session_get (ts->thread_index, ts->opaque);
643   if (!hs || !hs->data)
644     return 0;
645
646   to_send = hs->data_len - hs->data_offset;
647   rv = svm_fifo_enqueue (ts->tx_fifo, to_send, hs->data + hs->data_offset);
648
649   if (rv <= 0)
650     {
651       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
652       return 0;
653     }
654
655   if (rv < to_send)
656     {
657       hs->data_offset += rv;
658       svm_fifo_add_want_deq_ntf (ts->tx_fifo, SVM_FIFO_WANT_DEQ_NOTIF);
659     }
660
661   if (svm_fifo_set_event (ts->tx_fifo))
662     session_send_io_evt_to_thread (ts->tx_fifo, SESSION_IO_EVT_TX);
663
664   return 0;
665 }
666
667 /** \brief Session accept callback
668  */
669 static int
670 hss_ts_accept_callback (session_t *ts)
671 {
672   hss_session_t *hs;
673   u32 thresh;
674
675   hs = hss_session_alloc (ts->thread_index);
676
677   hs->vpp_session_index = ts->session_index;
678   hs->vpp_session_handle = session_handle (ts);
679
680   /* The application sets a threshold for it's fifo to get notified when
681    * additional data can be enqueued. We want to keep the TX fifo reasonably
682    * full, however avoid entering a state where the
683    * fifo is full all the time and small chunks of data are being enqueued
684    * each time. If the fifo is small (under 16K) we set
685    * the threshold to it's size, meaning a notification will be given when the
686    * fifo empties.
687    */
688   thresh = clib_min (svm_fifo_size (ts->tx_fifo), HSS_FIFO_THRESH);
689   svm_fifo_set_deq_thresh (ts->tx_fifo, thresh);
690
691   ts->opaque = hs->session_index;
692   ts->session_state = SESSION_STATE_READY;
693   return 0;
694 }
695
696 static void
697 hss_ts_disconnect_callback (session_t *ts)
698 {
699   hss_main_t *hsm = &hss_main;
700   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
701
702   a->handle = session_handle (ts);
703   a->app_index = hsm->app_index;
704   vnet_disconnect_session (a);
705 }
706
707 static void
708 hss_ts_reset_callback (session_t *ts)
709 {
710   hss_main_t *hsm = &hss_main;
711   vnet_disconnect_args_t _a = { 0 }, *a = &_a;
712
713   a->handle = session_handle (ts);
714   a->app_index = hsm->app_index;
715   vnet_disconnect_session (a);
716 }
717
718 static int
719 hss_ts_connected_callback (u32 app_index, u32 api_context, session_t *ts,
720                            session_error_t err)
721 {
722   clib_warning ("called...");
723   return -1;
724 }
725
726 static int
727 hss_add_segment_callback (u32 client_index, u64 segment_handle)
728 {
729   return 0;
730 }
731
732 static void
733 hss_ts_cleanup (session_t *s, session_cleanup_ntf_t ntf)
734 {
735   hss_session_t *hs;
736
737   if (ntf == SESSION_CLEANUP_TRANSPORT)
738     return;
739
740   hs = hss_session_get (s->thread_index, s->opaque);
741   if (!hs)
742     return;
743
744   hss_detach_cache_entry (hs);
745   hss_session_free (hs);
746 }
747
748 static session_cb_vft_t hss_cb_vft = {
749   .session_accept_callback = hss_ts_accept_callback,
750   .session_disconnect_callback = hss_ts_disconnect_callback,
751   .session_connected_callback = hss_ts_connected_callback,
752   .add_segment_callback = hss_add_segment_callback,
753   .builtin_app_rx_callback = hss_ts_rx_callback,
754   .builtin_app_tx_callback = hss_ts_tx_callback,
755   .session_reset_callback = hss_ts_reset_callback,
756   .session_cleanup_callback = hss_ts_cleanup,
757 };
758
759 static int
760 hss_attach ()
761 {
762   vnet_app_add_cert_key_pair_args_t _ck_pair, *ck_pair = &_ck_pair;
763   hss_main_t *hsm = &hss_main;
764   u64 options[APP_OPTIONS_N_OPTIONS];
765   vnet_app_attach_args_t _a, *a = &_a;
766   u32 segment_size = 128 << 20;
767
768   clib_memset (a, 0, sizeof (*a));
769   clib_memset (options, 0, sizeof (options));
770
771   if (hsm->private_segment_size)
772     segment_size = hsm->private_segment_size;
773
774   a->api_client_index = ~0;
775   a->name = format (0, "http_static_server");
776   a->session_cb_vft = &hss_cb_vft;
777   a->options = options;
778   a->options[APP_OPTIONS_SEGMENT_SIZE] = segment_size;
779   a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = segment_size;
780   a->options[APP_OPTIONS_RX_FIFO_SIZE] =
781     hsm->fifo_size ? hsm->fifo_size : 8 << 10;
782   a->options[APP_OPTIONS_TX_FIFO_SIZE] =
783     hsm->fifo_size ? hsm->fifo_size : 32 << 10;
784   a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN;
785   a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = hsm->prealloc_fifos;
786   a->options[APP_OPTIONS_TLS_ENGINE] = CRYPTO_ENGINE_OPENSSL;
787
788   if (vnet_application_attach (a))
789     {
790       vec_free (a->name);
791       clib_warning ("failed to attach server");
792       return -1;
793     }
794   vec_free (a->name);
795   hsm->app_index = a->app_index;
796
797   clib_memset (ck_pair, 0, sizeof (*ck_pair));
798   ck_pair->cert = (u8 *) test_srv_crt_rsa;
799   ck_pair->key = (u8 *) test_srv_key_rsa;
800   ck_pair->cert_len = test_srv_crt_rsa_len;
801   ck_pair->key_len = test_srv_key_rsa_len;
802   vnet_app_add_cert_key_pair (ck_pair);
803   hsm->ckpair_index = ck_pair->index;
804
805   return 0;
806 }
807
808 static int
809 hss_transport_needs_crypto (transport_proto_t proto)
810 {
811   return proto == TRANSPORT_PROTO_TLS || proto == TRANSPORT_PROTO_DTLS ||
812          proto == TRANSPORT_PROTO_QUIC;
813 }
814
815 static int
816 hss_listen (void)
817 {
818   hss_main_t *hsm = &hss_main;
819   session_endpoint_cfg_t sep = SESSION_ENDPOINT_CFG_NULL;
820   vnet_listen_args_t _a, *a = &_a;
821   char *uri = "tcp://0.0.0.0/80";
822   u8 need_crypto;
823   int rv;
824
825   clib_memset (a, 0, sizeof (*a));
826   a->app_index = hsm->app_index;
827
828   if (hsm->uri)
829     uri = (char *) hsm->uri;
830
831   if (parse_uri (uri, &sep))
832     return -1;
833
834   need_crypto = hss_transport_needs_crypto (a->sep_ext.transport_proto);
835
836   sep.transport_proto = TRANSPORT_PROTO_HTTP;
837   clib_memcpy (&a->sep_ext, &sep, sizeof (sep));
838
839   if (need_crypto)
840     {
841       session_endpoint_alloc_ext_cfg (&a->sep_ext,
842                                       TRANSPORT_ENDPT_EXT_CFG_CRYPTO);
843       a->sep_ext.ext_cfg->crypto.ckpair_index = hsm->ckpair_index;
844     }
845
846   rv = vnet_listen (a);
847
848   if (need_crypto)
849     clib_mem_free (a->sep_ext.ext_cfg);
850
851   return rv;
852 }
853
854 static void
855 hss_url_handlers_init (hss_main_t *hsm)
856 {
857   if (!hsm->get_url_handlers)
858     {
859       hsm->get_url_handlers = hash_create_string (0, sizeof (uword));
860       hsm->post_url_handlers = hash_create_string (0, sizeof (uword));
861     }
862
863   hss_builtinurl_json_handlers_init ();
864 }
865
866 int
867 hss_create (vlib_main_t *vm)
868 {
869   vlib_thread_main_t *vtm = vlib_get_thread_main ();
870   hss_main_t *hsm = &hss_main;
871   u32 num_threads;
872
873   num_threads = 1 /* main thread */  + vtm->n_threads;
874   vec_validate (hsm->sessions, num_threads - 1);
875
876   clib_spinlock_init (&hsm->cache_lock);
877
878   if (hss_attach ())
879     {
880       clib_warning ("failed to attach server");
881       return -1;
882     }
883   if (hss_listen ())
884     {
885       clib_warning ("failed to start listening");
886       return -1;
887     }
888
889   /* Init path-to-cache hash table */
890   BV (clib_bihash_init) (&hsm->name_to_data, "http cache", 128, 32 << 20);
891
892   if (hsm->enable_url_handlers)
893     hss_url_handlers_init (hsm);
894
895   return 0;
896 }
897
898 static clib_error_t *
899 hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input,
900                        vlib_cli_command_t *cmd)
901 {
902   unformat_input_t _line_input, *line_input = &_line_input;
903   hss_main_t *hsm = &hss_main;
904   clib_error_t *error = 0;
905   u64 seg_size;
906   int rv;
907
908   if (hsm->app_index != (u32) ~0)
909     return clib_error_return (0, "http server already running...");
910
911   hsm->prealloc_fifos = 0;
912   hsm->private_segment_size = 0;
913   hsm->fifo_size = 0;
914   /* 10mb cache limit, before LRU occurs */
915   hsm->cache_limit = 10 << 20;
916
917   /* Get a line of input. */
918   if (!unformat_user (input, unformat_line_input, line_input))
919     goto no_input;
920
921   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
922     {
923       if (unformat (line_input, "www-root %s", &hsm->www_root))
924         ;
925       else
926         if (unformat (line_input, "prealloc-fifos %d", &hsm->prealloc_fifos))
927         ;
928       else if (unformat (line_input, "private-segment-size %U",
929                          unformat_memory_size, &seg_size))
930         hsm->private_segment_size = seg_size;
931       else if (unformat (line_input, "fifo-size %d", &hsm->fifo_size))
932         hsm->fifo_size <<= 10;
933       else if (unformat (line_input, "cache-size %U", unformat_memory_size,
934                          &hsm->cache_limit))
935         ;
936       else if (unformat (line_input, "uri %s", &hsm->uri))
937         ;
938       else if (unformat (line_input, "debug %d", &hsm->debug_level))
939         ;
940       else if (unformat (line_input, "debug"))
941         hsm->debug_level = 1;
942       else if (unformat (line_input, "ptr-thresh %U", unformat_memory_size,
943                          &hsm->use_ptr_thresh))
944         ;
945       else if (unformat (line_input, "url-handlers"))
946         hsm->enable_url_handlers = 1;
947       else
948         {
949           error = clib_error_return (0, "unknown input `%U'",
950                                      format_unformat_error, line_input);
951         }
952     }
953
954   unformat_free (line_input);
955
956 no_input:
957
958   if (error)
959     goto done;
960
961   if (hsm->www_root == 0 && !hsm->enable_url_handlers)
962     {
963       error = clib_error_return (0, "Must set www-root or url-handlers");
964       goto done;
965     }
966
967   if (hsm->cache_limit < (128 << 10))
968     {
969       error = clib_error_return (0, "cache-size must be at least 128kb");
970       vec_free (hsm->www_root);
971       goto done;
972     }
973
974   vnet_session_enable_disable (vm, 1 /* turn on TCP, etc. */ );
975
976   if ((rv = hss_create (vm)))
977     {
978       error = clib_error_return (0, "server_create returned %d", rv);
979       vec_free (hsm->www_root);
980     }
981
982 done:
983
984   return error;
985 }
986
987 /*?
988  * Enable the static http server
989  *
990  * @cliexpar
991  * This command enables the static http server. Only the www-root
992  * parameter is required
993  * @clistart
994  * http static server www-root /tmp/www uri tcp://0.0.0.0/80 cache-size 2m
995  * @cliend
996  * @cliexcmd{http static server www-root <path> [prealloc-fios <nn>]
997  *   [private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]}
998 ?*/
999 VLIB_CLI_COMMAND (hss_create_command, static) = {
1000   .path = "http static server",
1001   .short_help =
1002     "http static server www-root <path> [prealloc-fifos <nn>]\n"
1003     "[private-segment-size <nnMG>] [fifo-size <nbytes>] [uri <uri>]\n"
1004     "[ptr-thresh <nn>] [url-handlers] [debug [nn]]\n",
1005   .function = hss_create_command_fn,
1006 };
1007
1008 /** \brief format a file cache entry
1009  */
1010 static u8 *
1011 format_hss_cache_entry (u8 *s, va_list *args)
1012 {
1013   hss_cache_entry_t *ep = va_arg (*args, hss_cache_entry_t *);
1014   f64 now = va_arg (*args, f64);
1015
1016   /* Header */
1017   if (ep == 0)
1018     {
1019       s = format (s, "%40s%12s%20s", "File", "Size", "Age");
1020       return s;
1021     }
1022   s = format (s, "%40s%12lld%20.2f", ep->filename, vec_len (ep->data),
1023               now - ep->last_used);
1024   return s;
1025 }
1026
1027 static u8 *
1028 format_hss_session (u8 *s, va_list *args)
1029 {
1030   hss_session_t *hs = va_arg (*args, hss_session_t *);
1031   int __clib_unused verbose = va_arg (*args, int);
1032
1033   s = format (s, "\n path %s, data length %u, data_offset %u",
1034               hs->path ? hs->path : (u8 *) "[none]", hs->data_len,
1035               hs->data_offset);
1036   return s;
1037 }
1038
1039 static clib_error_t *
1040 hss_show_command_fn (vlib_main_t *vm, unformat_input_t *input,
1041                      vlib_cli_command_t *cmd)
1042 {
1043   hss_main_t *hsm = &hss_main;
1044   hss_cache_entry_t *ep, **entries = 0;
1045   int verbose = 0;
1046   int show_cache = 0;
1047   int show_sessions = 0;
1048   u32 index;
1049   f64 now;
1050
1051   if (hsm->www_root == 0)
1052     return clib_error_return (0, "Static server disabled");
1053
1054   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
1055     {
1056       if (unformat (input, "verbose %d", &verbose))
1057         ;
1058       else if (unformat (input, "verbose"))
1059         verbose = 1;
1060       else if (unformat (input, "cache"))
1061         show_cache = 1;
1062       else if (unformat (input, "sessions"))
1063         show_sessions = 1;
1064       else
1065         break;
1066     }
1067
1068   if ((show_cache + show_sessions) == 0)
1069     return clib_error_return (0, "specify one or more of cache, sessions");
1070
1071   if (show_cache)
1072     {
1073       if (verbose == 0)
1074         {
1075           vlib_cli_output
1076             (vm, "www_root %s, cache size %lld bytes, limit %lld bytes, "
1077              "evictions %lld",
1078              hsm->www_root, hsm->cache_size, hsm->cache_limit,
1079              hsm->cache_evictions);
1080           return 0;
1081         }
1082
1083       now = vlib_time_now (vm);
1084
1085       vlib_cli_output (vm, "%U", format_hss_cache_entry, 0 /* header */, now);
1086
1087       for (index = hsm->first_index; index != ~0;)
1088         {
1089           ep = pool_elt_at_index (hsm->cache_pool, index);
1090           index = ep->next_index;
1091           vlib_cli_output (vm, "%U", format_hss_cache_entry, ep, now);
1092         }
1093
1094       vlib_cli_output (vm, "%40s%12lld", "Total Size", hsm->cache_size);
1095
1096       vec_free (entries);
1097     }
1098
1099   if (show_sessions)
1100     {
1101       u32 *session_indices = 0;
1102       hss_session_t *hs;
1103       int i, j;
1104
1105       hss_cache_lock ();
1106
1107       for (i = 0; i < vec_len (hsm->sessions); i++)
1108         {
1109           pool_foreach (hs, hsm->sessions[i])
1110            {
1111             vec_add1 (session_indices, hs - hsm->sessions[i]);
1112           }
1113
1114           for (j = 0; j < vec_len (session_indices); j++)
1115             {
1116               vlib_cli_output (
1117                 vm, "%U", format_hss_session,
1118                 pool_elt_at_index (hsm->sessions[i], session_indices[j]),
1119                 verbose);
1120             }
1121           vec_reset_length (session_indices);
1122         }
1123       hss_cache_unlock ();
1124       vec_free (session_indices);
1125     }
1126   return 0;
1127 }
1128
1129 /*?
1130  * Display static http server cache statistics
1131  *
1132  * @cliexpar
1133  * This command shows the contents of the static http server cache
1134  * @clistart
1135  * show http static server
1136  * @cliend
1137  * @cliexcmd{show http static server sessions cache [verbose [nn]]}
1138 ?*/
1139 VLIB_CLI_COMMAND (hss_show_command, static) = {
1140   .path = "show http static server",
1141   .short_help = "show http static server sessions cache [verbose [<nn>]]",
1142   .function = hss_show_command_fn,
1143 };
1144
1145 static clib_error_t *
1146 hss_clear_cache_command_fn (vlib_main_t *vm, unformat_input_t *input,
1147                             vlib_cli_command_t *cmd)
1148 {
1149   hss_main_t *hsm = &hss_main;
1150   hss_cache_entry_t *ce;
1151   u32 free_index;
1152   u32 busy_items = 0;
1153   BVT (clib_bihash_kv) kv;
1154
1155   if (hsm->www_root == 0)
1156     return clib_error_return (0, "Static server disabled");
1157
1158   hss_cache_lock ();
1159
1160   /* Walk the LRU list to find active entries */
1161   free_index = hsm->last_index;
1162   while (free_index != ~0)
1163     {
1164       ce = pool_elt_at_index (hsm->cache_pool, free_index);
1165       free_index = ce->prev_index;
1166       /* Which could be in use... */
1167       if (ce->inuse)
1168         {
1169           busy_items++;
1170           free_index = ce->next_index;
1171           continue;
1172         }
1173       kv.key = (u64) (ce->filename);
1174       kv.value = ~0ULL;
1175       if (BV (clib_bihash_add_del) (&hsm->name_to_data, &kv,
1176                                     0 /* is_add */ ) < 0)
1177         {
1178           clib_warning ("BUG: cache clear delete '%s' FAILED!", ce->filename);
1179         }
1180
1181       lru_remove (hsm, ce);
1182       hsm->cache_size -= vec_len (ce->data);
1183       hsm->cache_evictions++;
1184       vec_free (ce->filename);
1185       vec_free (ce->data);
1186       if (hsm->debug_level > 1)
1187         clib_warning ("pool put index %d", ce - hsm->cache_pool);
1188       pool_put (hsm->cache_pool, ce);
1189       free_index = hsm->last_index;
1190     }
1191   hss_cache_unlock ();
1192   if (busy_items > 0)
1193     vlib_cli_output (vm, "Note: %d busy items still in cache...", busy_items);
1194   else
1195     vlib_cli_output (vm, "Cache cleared...");
1196   return 0;
1197 }
1198
1199 /*?
1200  * Clear the static http server cache, to force the server to
1201  * reload content from backing files
1202  *
1203  * @cliexpar
1204  * This command clear the static http server cache
1205  * @clistart
1206  * clear http static cache
1207  * @cliend
1208  * @cliexcmd{clear http static cache}
1209 ?*/
1210 VLIB_CLI_COMMAND (clear_hss_cache_command, static) = {
1211   .path = "clear http static cache",
1212   .short_help = "clear http static cache",
1213   .function = hss_clear_cache_command_fn,
1214 };
1215
1216 static clib_error_t *
1217 hss_main_init (vlib_main_t *vm)
1218 {
1219   hss_main_t *hsm = &hss_main;
1220
1221   hsm->app_index = ~0;
1222   hsm->vlib_main = vm;
1223   hsm->first_index = hsm->last_index = ~0;
1224
1225   return 0;
1226 }
1227
1228 VLIB_INIT_FUNCTION (hss_main_init);
1229
1230 /*
1231  * fd.io coding-style-patch-verification: ON
1232  *
1233  * Local Variables:
1234  * eval: (c-set-style "gnu")
1235  * End:
1236  */