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