/* * Copyright (c) 2016 Cisco and/or its affiliates. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include ping_main_t ping_main; /** * @file * @brief IPv4 and IPv6 ICMP Ping. * * This file contains code to support IPv4 or IPv6 ICMP ECHO_REQUEST to * network hosts. * */ typedef struct { u16 id; u16 seq; u32 cli_process_node; u8 is_ip6; } icmp_echo_trace_t; u8 * format_icmp_echo_trace (u8 * s, va_list * va) { CLIB_UNUSED (vlib_main_t * vm) = va_arg (*va, vlib_main_t *); CLIB_UNUSED (vlib_node_t * node) = va_arg (*va, vlib_node_t *); icmp_echo_trace_t *t = va_arg (*va, icmp_echo_trace_t *); s = format (s, "ICMP%s echo id %d seq %d", t->is_ip6 ? "6" : "4", t->id, t->seq); if (t->cli_process_node == PING_CLI_UNKNOWN_NODE) { s = format (s, " (unknown)"); } else { s = format (s, " send to cli node %d", t->cli_process_node); } return s; } static u8 * format_ip46_ping_result (u8 * s, va_list * args) { send_ip46_ping_result_t res = va_arg (*args, send_ip46_ping_result_t); switch (res) { #define _(v, n) case SEND_PING_##v: s = format(s, "%s", n);break; foreach_ip46_ping_result #undef _ } return (s); } /* * Poor man's get-set-clear functions * for manipulation of icmp_id -> cli_process_id * mappings. * * There should normally be very few (0..1..2) of these * mappings, so the linear search is a good strategy. * * Make them thread-safe via a simple spinlock. * */ static_always_inline uword get_cli_process_id_by_icmp_id_mt (vlib_main_t * vm, u16 icmp_id) { ping_main_t *pm = &ping_main; uword cli_process_id = PING_CLI_UNKNOWN_NODE; ping_run_t *pr; clib_spinlock_lock_if_init (&pm->ping_run_check_lock); vec_foreach (pr, pm->active_ping_runs) { if (pr->icmp_id == icmp_id) { cli_process_id = pr->cli_process_id; break; } } clib_spinlock_unlock_if_init (&pm->ping_run_check_lock); return cli_process_id; } static_always_inline void set_cli_process_id_by_icmp_id_mt (vlib_main_t * vm, u16 icmp_id, uword cli_process_id) { ping_main_t *pm = &ping_main; ping_run_t *pr; clib_spinlock_lock_if_init (&pm->ping_run_check_lock); vec_foreach (pr, pm->active_ping_runs) { if (pr->icmp_id == icmp_id) { pr->cli_process_id = cli_process_id; goto have_found_and_set; } } /* no such key yet - add a new one */ ping_run_t new_pr = {.icmp_id = icmp_id,.cli_process_id = cli_process_id }; vec_add1 (pm->active_ping_runs, new_pr); have_found_and_set: clib_spinlock_unlock_if_init (&pm->ping_run_check_lock); } static_always_inline void clear_cli_process_id_by_icmp_id_mt (vlib_main_t * vm, u16 icmp_id) { ping_main_t *pm = &ping_main; ping_run_t *pr; clib_spinlock_lock_if_init (&pm->ping_run_check_lock); vec_foreach (pr, pm->active_ping_runs) { if (pr->icmp_id == icmp_id) { vec_del1 (pm->active_ping_runs, pm->active_ping_runs - pr); break; } } clib_spinlock_unlock_if_init (&pm->ping_run_check_lock); } static_always_inline int ip46_get_icmp_id_and_seq (vlib_main_t * vm, vlib_buffer_t * b0, u16 * out_icmp_id, u16 * out_icmp_seq, int is_ip6) { int l4_offset; if (is_ip6) { ip6_header_t *ip6 = vlib_buffer_get_current (b0); if (ip6->protocol != IP_PROTOCOL_ICMP6) { return 0; } l4_offset = sizeof (*ip6); // IPv6 EH } else { ip4_header_t *ip4 = vlib_buffer_get_current (b0); l4_offset = ip4_header_bytes (ip4); } icmp46_header_t *icmp46 = vlib_buffer_get_current (b0) + l4_offset; icmp46_echo_request_t *icmp46_echo = (icmp46_echo_request_t *) (icmp46 + 1); *out_icmp_id = clib_net_to_host_u16 (icmp46_echo->id); *out_icmp_seq = clib_net_to_host_u16 (icmp46_echo->seq); return 1; } /* * post the buffer to a given cli process node - the caller should forget bi0 after return. */ static_always_inline void ip46_post_icmp_reply_event (vlib_main_t * vm, uword cli_process_id, u32 bi0, int is_ip6) { vlib_buffer_t *b0 = vlib_get_buffer (vm, bi0); u64 nowts = clib_cpu_time_now (); /* Pass the timestamp to the cli_process thanks to the vnet_buffer unused metadata field */ /* Camping on unused data... just ensure statically that there is enough space */ STATIC_ASSERT (ARRAY_LEN (vnet_buffer (b0)->unused) * sizeof (vnet_buffer (b0)->unused[0]) > sizeof (nowts), "ping reply timestamp fits within remaining space of vnet_buffer unused data"); u64 *pnowts = (void *) &vnet_buffer (b0)->unused[0]; *pnowts = nowts; u32 event_id = is_ip6 ? PING_RESPONSE_IP6 : PING_RESPONSE_IP4; vlib_process_signal_event_mt (vm, cli_process_id, event_id, bi0); } static_always_inline void ip46_echo_reply_maybe_trace_buffer (vlib_main_t * vm, vlib_node_runtime_t * node, uword cli_process_id, u16 id, u16 seq, vlib_buffer_t * b0, int is_ip6) { if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) { icmp_echo_trace_t *tr = vlib_add_trace (vm, node, b0, sizeof (*tr)); tr->id = id; tr->seq = seq; tr->cli_process_node = cli_process_id; tr->is_ip6 = is_ip6; } } static_always_inline uword ip46_icmp_echo_reply_inner_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame, int do_trace, int is_ip6) { u32 n_left_from, *from, *to_next; icmp46_echo_reply_next_t next_index; from = vlib_frame_vector_args (frame); n_left_from = frame->n_vectors; next_index = node->cached_next_index; while (n_left_from > 0) { u32 n_left_to_next; vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); while (n_left_from > 0 && n_left_to_next > 0) { u32 bi0; vlib_buffer_t *b0; /* * The buffers (replies) are either posted to the CLI thread * awaiting for them for subsequent analysis and disposal, * or are sent to the punt node. * * So the only "next" node is a punt, normally. */ u32 next0 = ICMP46_ECHO_REPLY_NEXT_PUNT; bi0 = from[0]; b0 = vlib_get_buffer (vm, bi0); from += 1; n_left_from -= 1; u16 icmp_id = ~0; u16 icmp_seq = ~0; uword cli_process_id = PING_CLI_UNKNOWN_NODE; if (ip46_get_icmp_id_and_seq (vm, b0, &icmp_id, &icmp_seq, is_ip6)) { cli_process_id = get_cli_process_id_by_icmp_id_mt (vm, icmp_id); } if (do_trace) ip46_echo_reply_maybe_trace_buffer (vm, node, cli_process_id, icmp_id, icmp_seq, b0, is_ip6); if (~0 == cli_process_id) { /* no outstanding requests for this reply, punt */ /* speculatively enqueue b0 to the current next frame */ to_next[0] = bi0; to_next += 1; n_left_to_next -= 1; /* verify speculative enqueue, maybe switch current next frame */ vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, n_left_to_next, bi0, next0); } else { /* Post the buffer to CLI thread. It will take care of freeing it. */ ip46_post_icmp_reply_event (vm, cli_process_id, bi0, is_ip6); } } vlib_put_next_frame (vm, node, next_index, n_left_to_next); } return frame->n_vectors; } /* * select "with-trace" or "without-trace" codepaths upfront. */ static_always_inline uword ip46_icmp_echo_reply_outer_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame, int is_ip6) { if (node->flags & VLIB_NODE_FLAG_TRACE) return ip46_icmp_echo_reply_inner_node_fn (vm, node, frame, 1 /* do_trace */ , is_ip6); else return ip46_icmp_echo_reply_inner_node_fn (vm, node, frame, 0 /* do_trace */ , is_ip6); } static uword ip4_icmp_echo_reply_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) { return ip46_icmp_echo_reply_outer_node_fn (vm, node, frame, 0 /* is_ip6 */ ); } static uword ip6_icmp_echo_reply_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) { return ip46_icmp_echo_reply_outer_node_fn (vm, node, frame, 1 /* is_ip6 */ ); } /* *INDENT-OFF* */ VLIB_REGISTER_NODE (ip6_icmp_echo_reply_node, static) = { .function = ip6_icmp_echo_reply_node_fn, .name = "ip6-icmp-echo-reply", .vector_size = sizeof (u32), .format_trace = format_icmp_echo_trace, .n_next_nodes = ICMP46_ECHO_REPLY_N_NEXT, .next_nodes = { [ICMP46_ECHO_REPLY_NEXT_DROP] = "ip6-drop", [ICMP46_ECHO_REPLY_NEXT_PUNT] = "ip6-punt", }, }; VLIB_REGISTER_NODE (ip4_icmp_echo_reply_node, static) = { .function = ip4_icmp_echo_reply_node_fn, .name = "ip4-icmp-echo-reply", .vector_size = sizeof (u32), .format_trace = format_icmp_echo_trace, .n_next_nodes = ICMP46_ECHO_REPLY_N_NEXT, .next_nodes = { [ICMP46_ECHO_REPLY_NEXT_DROP] = "ip4-drop", [ICMP46_ECHO_REPLY_NEXT_PUNT] = "ip4-punt", }, }; /* *INDENT-ON* */ /* * A swarm of address-family agnostic helper functions * for building and sending the ICMP echo request. * * Deliberately mostly "static" rather than "static inline" * so one can trace them sanely if needed in debugger, if needed. * */ static_always_inline u8 get_icmp_echo_payload_byte (int offset) { return (offset % 256); } /* Fill in the ICMP ECHO structure, return the safety-checked and possibly shrunk data_len */ static u16 init_icmp46_echo_request (vlib_main_t * vm, vlib_buffer_t * b0, int l4_header_offset, icmp46_echo_request_t * icmp46_echo, u16 seq_host, u16 id_host, u64 now, u16 data_len) { int i; int l34_len = l4_header_offset + sizeof (icmp46_header_t) + offsetof (icmp46_echo_request_t, data); int max_data_len = vlib_buffer_get_default_data_size (vm) - l34_len; int first_buf_data_len = data_len < max_data_len ? data_len : max_data_len; int payload_offset = 0; for (i = 0; i < first_buf_data_len; i++) icmp46_echo->data[i] = get_icmp_echo_payload_byte (payload_offset++); /* inspired by vlib_buffer_add_data */ vlib_buffer_t *hb = b0; int remaining_data_len = data_len - first_buf_data_len; while (remaining_data_len) { int this_buf_data_len = remaining_data_len < vlib_buffer_get_default_data_size (vm) ? remaining_data_len : vlib_buffer_get_default_data_size (vm); int n_alloc = vlib_buffer_alloc (vm, &b0->next_buffer, 1); if (n_alloc < 1) { /* That is how much we have so far - return it... */ return (data_len - remaining_data_len); } b0->flags |= VLIB_BUFFER_NEXT_PRESENT; /* move on to the newly acquired buffer */ b0 = vlib_get_buffer (vm, b0->next_buffer); /* initialize the data */ for (i = 0; i < this_buf_data_len; i++) { b0->data[i] = get_icmp_echo_payload_byte (payload_offset++); } b0->current_length = this_buf_data_len; b0->current_data = 0; remaining_data_len -= this_buf_data_len; } hb->flags |= VLIB_BUFFER_TOTAL_LENGTH_VALID; hb->current_length = l34_len + first_buf_data_len; hb->total_length_not_including_first_buffer = data_len - first_buf_data_len; icmp46_echo->time_sent = now; icmp46_echo->seq = clib_host_to_net_u16 (seq_host); icmp46_echo->id = clib_host_to_net_u16 (id_host); return data_len; } static u32 ip46_fib_index_from_table_id (u32 table_id, int is_ip6) { u32 fib_index = is_ip6 ? ip6_fib_index_from_table_id (table_id) : ip4_fib_index_from_table_id (table_id); return fib_index; } static fib_node_index_t ip46_fib_table_lookup_host (u32 fib_index, ip46_address_t * pa46, int is_ip6) { fib_node_index_t fib_entry_index = is_ip6 ? ip6_fib_table_lookup (fib_index, &pa46->ip6, 128) : ip4_fib_table_lookup (ip4_fib_get (fib_index), &pa46->ip4, 32); return fib_entry_index; } static u32 ip46_get_resolving_interface (u32 fib_index, ip46_address_t * pa46, int is_ip6) { u32 sw_if_index = ~0; if (~0 != fib_index) { fib_node_index_t fib_entry_index; fib_entry_index = ip46_fib_table_lookup_host (fib_index, pa46, is_ip6); sw_if_index = fib_entry_get_resolving_interface (fib_entry_index); } return sw_if_index; } static u32 ip46_fib_table_get_index_for_sw_if_index (u32 sw_if_index, int is_ip6) { u32 fib_table_index = is_ip6 ? ip6_fib_table_get_index_for_sw_if_index (sw_if_index) : ip4_fib_table_get_index_for_sw_if_index (sw_if_index); return fib_table_index; } static int ip46_fill_l3_header (ip46_address_t * pa46, vlib_buffer_t * b0, int is_ip6) { if (is_ip6) { ip6_header_t *ip6 = vlib_buffer_get_current (b0); /* Fill in ip6 header fields */ ip6->ip_version_traffic_class_and_flow_label = clib_host_to_net_u32 (0x6 << 28); ip6->payload_length = 0; /* will be set later */ ip6->protocol = IP_PROTOCOL_ICMP6; ip6->hop_limit = 255; ip6->dst_address = pa46->ip6; ip6->src_address = pa46->ip6; return (sizeof (ip6_header_t)); } else { ip4_header_t *ip4 = vlib_buffer_get_current (b0); /* Fill in ip4 header fields */ ip4->checksum = 0; ip4->ip_version_and_header_
Web applications with VPP
=========================

Vpp includes a versatile http/https “static” server plugin. We quote the
word static in the previous sentence because the server is easily
extended. This note describes how to build a Hugo site which includes
both monitoring and control functions.

Let’s assume that we have a vpp data-plane plugin which needs a
monitoring and control web application. Here’s how to build one.

Step 1: Add URL handlers
------------------------

Individual URL handlers are pretty straightforward. You can return just
about anything you like, but as we work through the example you’ll see
why returning data in .json format tends to work out pretty well.

::

       static int
       handle_get_status (http_builtin_method_type_t reqtype,
                        u8 * request, http_session_t * hs)
       {
         my_main_t *mm = &my_main;
         u8 *s = 0;

         /* Construct a .json reply */
         s = format (s, "{\"status\": {");
         s = format (s, "   \"thing1\": \"%s\",", mm->thing1_value_string);
         s = format (s, "   \"thing2\": \"%s\",", mm->thing2_value_string);
         /* ... etc ... */
         s = format (s, "   \"lastthing\": \"%s\"", mm->last_value_string);
         s = format (s, "}}");

         /* And tell the static server plugin how to send the results */
         hs->data = s;
         hs->data_offset = 0;
         hs->cache_pool_index = ~0;
         hs->free_data = 1; /* free s when done with it, in the framework */
         return 0;
       }

Words to the Wise: Chrome has a very nice set of debugging tools. Select
“More Tools -> Developer Tools”. Right-hand sidebar appears with html
source code, a javascript debugger, network results including .json
objects, and so on.

Note: .json object format is **intolerant** of both missing and extra
commas, missing and extra curly-braces. It’s easy to waste a
considerable amount of time debugging .json bugs.

Step 2: Register URL handlers with the server
---------------------------------------------

Call http_static_server_register_builtin_handler() as shown. It’s likely
but not guaranteed that the static server plugin will be available.

::

       int
       plugin_url_init (vlib_main_t * vm)
       {
         void (*fp) (void *, char *, int);

         /* Look up the builtin URL registration handler */
         fp = vlib_get_plugin_symbol ("http_static_plugin.so",
                          "http_static_server_register_builtin_handler");

         if (fp == 0)
           {
             clib_warning ("http_static_plugin.so not loaded...");
             return -1;
           }

         (*fp) (handle_get_status, "status.json", HTTP_BUILTIN_METHOD_GET);
         (*fp) (handle_get_run, "run.json", HTTP_BUILTIN_METHOD_GET);
         (*fp) (handle_get_reset, "reset.json", HTTP_BUILTIN_METHOD_GET);
         (*fp) (handle_get_stop, "stop.json", HTTP_BUILTIN_METHOD_GET);
         return 0;
         }

Make sure to start the http static server **before** calling
plugin_url_init(…), or the registrations will disappear.

Step 3: Install Hugo, pick a theme, and create a site
-----------------------------------------------------

Please refer to the Hugo documentation.

See `the Hugo Quick Start
Page <https://gohugo.io/getting-started/quick-start>`__. Prebuilt binary
artifacts for many different environments are available on `the Hugo
release page <https://github.com/gohugoio/hugo/releases>`__.

To pick a theme, visit `the Hugo Theme
site <https://themes.gohugo.io>`__. Decide what you need your site to
look like. Stay away from complex themes unless you’re prepared to spend
considerable time tweaking and tuning.

The “Introduction” theme is a good choice for a simple site, YMMV.

Step 4: Create a “rawhtml” shortcode
------------------------------------

Once you’ve initialized your new site, create the directory
/layouts/shortcodes. Create the file “rawhtml.html” in that directory,
with the following contents:

::

       <!-- raw html -->
       {{.Inner}}

This is a key trick which allows a static Hugo site to include
javascript code.

Step 5: create Hugo content which interacts with vpp
----------------------------------------------------

Now it’s time to do some web front-end coding in javascript. Of course,
you can create static text, images, etc. as described in the Hugo
documentation. Nothing changes in that respect.

To include dynamically-generated data in your Hugo pages, splat down
some

.. raw:: html

   <div>

HTML tags, and define a few buttons:

::

       {{< rawhtml >}}
       <div id="Thing1"></div>
       <div id="Thing2"></div>
       <div id="Lastthing"></div>
       <input type="button" value="Run" onclick="runButtonClick()">
       <input type="button" value="Reset" onclick="resetButtonClick()">
       <input type="button" value="Stop" onclick="stopButtonClick()">
       <div id="Message"></div>
       {{< /rawhtml >}}

Time for some javascript code to interact with vpp:

::

   {{< rawhtml >}}
   <script>
   async function getStatusJson() {
       pump_url = location.href + "status.json";
       const json = await fetch(pump_url, {
           method: 'GET',
           mode: 'no-cors',
           cache: 'no-cache',
           headers: {
               'Content-Type': 'application/json',
           },
       })
       .then((response) => response.json())
       .catch(function(error) {
           console.log(error);
       });

       return json.status;
   };

   async function sendButton(which) {
       my_url = location.href + which + ".json";
       const json = await fetch(my_url, {
           method: 'GET',
           mode: 'no-cors',
           cache: 'no-cache',
           headers: {
               'Content-Type': 'application/json',
           },
       })
       .then((response) => response.json())
       .catch(function(error) {
           console.log(error);
       });
       return json.message;
   };

   async function getStatus() {
         const status = await getStatusJson();

         document.getElementById("Thing1").innerHTML = status.thing1;
         document.getElementById("Thing2").innerHTML = status.thing2;
         document.getElementById("Lastthing").innerHTML = status.lastthing;
   };

   async function runButtonClick() {
         const json = await sendButton("run");
         document.getElementById("Message").innerHTML = json.Message;
   }

   async function resetButtonClick() {
         const json = await sendButton("reset");
         document.getElementById("Message").innerHTML = json.Message;
   }
   async function stopButtonClick() {
         const json = await sendButton("stop");
         document.getElementById("Message").innerHTML = json.Message;
   }

   getStatus();

   </script>
   {{< /rawhtml >}}

At this level, javascript coding is pretty simple. Unless you know
exactly what you’re doing, please follow the async function / await
pattern shown above.

Step 6: compile the website
---------------------------

At the top of the website workspace, simply type “hugo”. The compiled
website lands in the “public” subdirectory.

You can use the Hugo static server - with suitable stub javascript code
- to see what your site will eventually look like. To start the hugo
static server, type “hugo server”. Browse to “http://localhost:1313”.

Step 7: configure vpp
---------------------

In terms of command-line args: you may wish to use poll-sleep-usec 100
to keep the load average low. Totally appropriate if vpp won’t be
processing a lot of packets or handling high-rate http/https traffic.

::

      unix {
        ...
        poll-sleep-usec 100
        startup-config ... see below ...
        ...
       }

If you wish to provide an https site, configure tls. The simplest tls
configuration uses a built-in test certificate - which will annoy Chrome
/ Firefox - but it’s sufficient for testing:

::

       tls {
           use-test-cert-in-ca
       }

vpp startup configuration
~~~~~~~~~~~~~~~~~~~~~~~