summaryrefslogtreecommitdiffstats
path: root/src/plugins/flowperpkt/flowperpkt.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/flowperpkt/flowperpkt.c')
-rw-r--r--src/plugins/flowperpkt/flowperpkt.c671
1 files changed, 671 insertions, 0 deletions
diff --git a/src/plugins/flowperpkt/flowperpkt.c b/src/plugins/flowperpkt/flowperpkt.c
new file mode 100644
index 00000000000..fb71d5b0ffc
--- /dev/null
+++ b/src/plugins/flowperpkt/flowperpkt.c
@@ -0,0 +1,671 @@
+/*
+ * flowperpkt.c - per-packet data capture flow report plugin
+ *
+ * Copyright (c) <current-year> <your-organization>
+ * 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.
+ */
+
+/**
+ * @file
+ * @brief Per-packet IPFIX flow record generator plugin
+ *
+ * This file implements vpp plugin registration mechanics,
+ * debug CLI, and binary API handling.
+ */
+
+#include <vnet/vnet.h>
+#include <vnet/plugin/plugin.h>
+#include <flowperpkt/flowperpkt.h>
+
+#include <vlibapi/api.h>
+#include <vlibmemory/api.h>
+#include <vlibsocket/api.h>
+
+/* define message IDs */
+#include <flowperpkt/flowperpkt_msg_enum.h>
+
+/* define message structures */
+#define vl_typedefs
+#include <flowperpkt/flowperpkt_all_api_h.h>
+#undef vl_typedefs
+
+/* define generated endian-swappers */
+#define vl_endianfun
+#include <flowperpkt/flowperpkt_all_api_h.h>
+#undef vl_endianfun
+
+/* instantiate all the print functions we know about */
+#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
+#define vl_printfun
+#include <flowperpkt/flowperpkt_all_api_h.h>
+#undef vl_printfun
+
+flowperpkt_main_t flowperpkt_main;
+
+/* Get the API version number */
+#define vl_api_version(n,v) static u32 api_version=(v);
+#include <flowperpkt/flowperpkt_all_api_h.h>
+#undef vl_api_version
+
+/* Define the per-interface configurable features */
+/* *INDENT-OFF* */
+VNET_FEATURE_INIT (flow_perpacket_ipv4, static) =
+{
+ .arc_name = "ip4-output",
+ .node_name = "flowperpkt-ipv4",
+ .runs_before = VNET_FEATURES ("interface-output"),
+};
+
+VNET_FEATURE_INIT (flow_perpacket_l2, static) =
+{
+ .arc_name = "interface-output",
+ .node_name = "flowperpkt-l2",
+ .runs_before = VNET_FEATURES ("interface-tx"),
+};
+/* *INDENT-ON* */
+
+/*
+ * A handy macro to set up a message reply.
+ * Assumes that the following variables are available:
+ * mp - pointer to request message
+ * rmp - pointer to reply message type
+ * rv - return value
+ */
+#define REPLY_MACRO(t) \
+do { \
+ unix_shared_memory_queue_t * q = \
+ vl_api_client_index_to_input_queue (mp->client_index); \
+ if (!q) \
+ return; \
+ \
+ rmp = vl_msg_api_alloc (sizeof (*rmp)); \
+ rmp->_vl_msg_id = ntohs((t)+fm->msg_id_base); \
+ rmp->context = mp->context; \
+ rmp->retval = ntohl(rv); \
+ \
+ vl_msg_api_send_shmem (q, (u8 *)&rmp); \
+} while(0);
+
+/* Macro to finish up custom dump fns */
+#define FINISH \
+ vec_add1 (s, 0); \
+ vl_print (handle, (char *)s); \
+ vec_free (s); \
+ return handle;
+
+#define VALIDATE_SW_IF_INDEX(mp) \
+ do { u32 __sw_if_index = ntohl(mp->sw_if_index); \
+ vnet_main_t *__vnm = vnet_get_main(); \
+ if (pool_is_free_index(__vnm->interface_main.sw_interfaces, \
+ __sw_if_index)) { \
+ rv = VNET_API_ERROR_INVALID_SW_IF_INDEX; \
+ goto bad_sw_if_index; \
+ } \
+} while(0);
+
+#define BAD_SW_IF_INDEX_LABEL \
+do { \
+bad_sw_if_index: \
+ ; \
+} while (0);
+
+/**
+ * @brief Create an IPFIX template packet rewrite string
+ * @param frm flow_report_main_t *
+ * @param fr flow_report_t *
+ * @param collector_address ip4_address_t * the IPFIX collector address
+ * @param src_address ip4_address_t * the source address we should use
+ * @param collector_port u16 the collector port we should use, host byte order
+ * @returns u8 * vector containing the indicated IPFIX template packet
+ */
+static inline u8 *
+flowperpkt_template_rewrite_inline (flow_report_main_t * frm,
+ flow_report_t * fr,
+ ip4_address_t * collector_address,
+ ip4_address_t * src_address,
+ u16 collector_port, int variant)
+{
+ ip4_header_t *ip;
+ udp_header_t *udp;
+ ipfix_message_header_t *h;
+ ipfix_set_header_t *s;
+ ipfix_template_header_t *t;
+ ipfix_field_specifier_t *f;
+ ipfix_field_specifier_t *first_field;
+ u8 *rewrite = 0;
+ ip4_ipfix_template_packet_t *tp;
+ u32 field_count = 0;
+ flow_report_stream_t *stream;
+ flowperpkt_main_t *fm = &flowperpkt_main;
+
+ stream = &frm->streams[fr->stream_index];
+
+ if (variant == FLOW_VARIANT_IPV4)
+ {
+ /*
+ * ip4 Supported Fields:
+ *
+ * ingressInterface, TLV type 10, u32
+ * egressInterface, TLV type 14, u32
+ * sourceIpv4Address, TLV type 8, u32
+ * destinationIPv4Address, TLV type 12, u32
+ * ipClassOfService, TLV type 5, u8
+ * flowStartNanoseconds, TLV type 156, dateTimeNanoseconds (f64)
+ * Implementation: f64 nanoseconds since VPP started
+ * warning: wireshark doesn't really understand this TLV
+ * dataLinkFrameSize, TLV type 312, u16
+ * warning: wireshark doesn't understand this TLV at all
+ */
+
+ /* Currently 7 fields */
+ field_count += 7;
+
+ /* allocate rewrite space */
+ vec_validate_aligned
+ (rewrite,
+ sizeof (ip4_ipfix_template_packet_t)
+ + field_count * sizeof (ipfix_field_specifier_t) - 1,
+ CLIB_CACHE_LINE_BYTES);
+ }
+ else if (variant == FLOW_VARIANT_L2)
+ {
+ /*
+ * L2 Supported Fields:
+ *
+ * ingressInterface, TLV type 10, u32
+ * egressInterface, TLV type 14, u32
+ * sourceMacAddress, TLV type 56, u8[6] we hope
+ * destinationMacAddress, TLV type 57, u8[6] we hope
+ * ethernetType, TLV type 256, u16
+ * flowStartNanoseconds, TLV type 156, dateTimeNanoseconds (f64)
+ * Implementation: f64 nanoseconds since VPP started
+ * warning: wireshark doesn't really understand this TLV
+ * dataLinkFrameSize, TLV type 312, u16
+ * warning: wireshark doesn't understand this TLV at all
+ */
+
+ /* Currently 7 fields */
+ field_count += 7;
+
+ /* allocate rewrite space */
+ vec_validate_aligned
+ (rewrite,
+ sizeof (ip4_ipfix_template_packet_t)
+ + field_count * sizeof (ipfix_field_specifier_t) - 1,
+ CLIB_CACHE_LINE_BYTES);
+ }
+
+ tp = (ip4_ipfix_template_packet_t *) rewrite;
+ ip = (ip4_header_t *) & tp->ip4;
+ udp = (udp_header_t *) (ip + 1);
+ h = (ipfix_message_header_t *) (udp + 1);
+ s = (ipfix_set_header_t *) (h + 1);
+ t = (ipfix_template_header_t *) (s + 1);
+ first_field = f = (ipfix_field_specifier_t *) (t + 1);
+
+ ip->ip_version_and_header_length = 0x45;
+ ip->ttl = 254;
+ ip->protocol = IP_PROTOCOL_UDP;
+ ip->src_address.as_u32 = src_address->as_u32;
+ ip->dst_address.as_u32 = collector_address->as_u32;
+ udp->src_port = clib_host_to_net_u16 (stream->src_port);
+ udp->dst_port = clib_host_to_net_u16 (collector_port);
+ udp->length = clib_host_to_net_u16 (vec_len (rewrite) - sizeof (*ip));
+
+ /* FIXUP: message header export_time */
+ /* FIXUP: message header sequence_number */
+ h->domain_id = clib_host_to_net_u32 (stream->domain_id);
+
+ /* Add TLVs to the template */
+ if (variant == FLOW_VARIANT_IPV4)
+ {
+ f->e_id_length =
+ ipfix_e_id_length (0 /* enterprise */ , ingressInterface,
+ 4);
+ f++;
+ f->e_id_length =
+ ipfix_e_id_length (0 /* enterprise */ , egressInterface,
+ 4);
+ f++;
+ f->e_id_length =
+ ipfix_e_id_length (0 /* enterprise */ , sourceIPv4Address,
+ 4);
+ f++;
+ f->e_id_length =
+ ipfix_e_id_length (0 /* enterprise */ , destinationIPv4Address, 4);
+ f++;
+ f->e_id_length =
+ ipfix_e_id_length (0 /* enterprise */ , ipClassOfService,
+ 1);
+ f++;
+ f->e_id_length =
+ ipfix_e_id_length (0 /* enterprise */ , flowStartNanoseconds,
+ 8);
+ f++;
+ f->e_id_length =
+ ipfix_e_id_length (0 /* enterprise */ , dataLinkFrameSize,
+ 2);
+ f++;
+ }
+ else if (variant == FLOW_VARIANT_L2)
+ {
+ f->e_id_length =
+ ipfix_e_id_length (0 /* enterprise */ , ingressInterface,
+ 4);
+ f++;
+ f->e_id_length =
+ ipfix_e_id_length (0 /* enterprise */ , egressInterface,
+ 4);
+ f++;
+ f->e_id_length =
+ ipfix_e_id_length (0 /* enterprise */ , sourceMacAddress,
+ 6);
+ f++;
+ f->e_id_length =
+ ipfix_e_id_length (0 /* enterprise */ , destinationMacAddress, 6);
+ f++;
+ f->e_id_length = ipfix_e_id_length (0 /* enterprise */ , ethernetType,
+ 2);
+ f++;
+ f->e_id_length =
+ ipfix_e_id_length (0 /* enterprise */ , flowStartNanoseconds,
+ 8);
+ f++;
+ f->e_id_length =
+ ipfix_e_id_length (0 /* enterprise */ , dataLinkFrameSize,
+ 2);
+ f++;
+ }
+
+ /* Extend in the obvious way, right here... */
+
+ /* Back to the template packet... */
+ ip = (ip4_header_t *) & tp->ip4;
+ udp = (udp_header_t *) (ip + 1);
+
+ ASSERT (f - first_field);
+ /* Field count in this template */
+ t->id_count = ipfix_id_count (fr->template_id, f - first_field);
+
+ if (variant == FLOW_VARIANT_IPV4)
+ fm->ipv4_report_id = fr->template_id;
+ else if (variant == FLOW_VARIANT_L2)
+ fm->l2_report_id = fr->template_id;
+
+ /* set length in octets */
+ s->set_id_length =
+ ipfix_set_id_length (2 /* set_id */ , (u8 *) f - (u8 *) s);
+
+ /* message length in octets */
+ h->version_length = version_length ((u8 *) f - (u8 *) h);
+
+ ip->length = clib_host_to_net_u16 ((u8 *) f - (u8 *) ip);
+ ip->checksum = ip4_header_checksum (ip);
+
+ return rewrite;
+}
+
+u8 *
+flowperpkt_template_rewrite_ipv4 (flow_report_main_t * frm,
+ flow_report_t * fr,
+ ip4_address_t * collector_address,
+ ip4_address_t * src_address,
+ u16 collector_port)
+{
+ return flowperpkt_template_rewrite_inline
+ (frm, fr, collector_address, src_address, collector_port,
+ FLOW_VARIANT_IPV4);
+}
+
+u8 *
+flowperpkt_template_rewrite_l2 (flow_report_main_t * frm,
+ flow_report_t * fr,
+ ip4_address_t * collector_address,
+ ip4_address_t * src_address,
+ u16 collector_port)
+{
+ return flowperpkt_template_rewrite_inline
+ (frm, fr, collector_address, src_address, collector_port,
+ FLOW_VARIANT_L2);
+}
+
+
+/**
+ * @brief Flush accumulated data
+ * @param frm flow_report_main_t *
+ * @param fr flow_report_t *
+ * @param f vlib_frame_t *
+ *
+ * <em>Notes:</em>
+ * This function must simply return the incoming frame, or no template packets
+ * will be sent.
+ */
+vlib_frame_t *
+flowperpkt_data_callback_ipv4 (flow_report_main_t * frm,
+ flow_report_t * fr,
+ vlib_frame_t * f, u32 * to_next,
+ u32 node_index)
+{
+ flowperpkt_flush_callback_ipv4 ();
+ return f;
+}
+
+vlib_frame_t *
+flowperpkt_data_callback_l2 (flow_report_main_t * frm,
+ flow_report_t * fr,
+ vlib_frame_t * f, u32 * to_next, u32 node_index)
+{
+ flowperpkt_flush_callback_l2 ();
+ return f;
+}
+
+/**
+ * @brief configure / deconfigure the IPFIX flow-per-packet
+ * @param fm flowperpkt_main_t * fm
+ * @param sw_if_index u32 the desired interface
+ * @param is_add int 1 to enable the feature, 0 to disable it
+ * @returns 0 if successful, non-zero otherwise
+ */
+
+static int flowperpkt_tx_interface_add_del_feature
+ (flowperpkt_main_t * fm, u32 sw_if_index, int which, int is_add)
+{
+ flow_report_main_t *frm = &flow_report_main;
+ vnet_flow_report_add_del_args_t _a, *a = &_a;
+ int rv;
+
+ if (which == FLOW_VARIANT_IPV4 && !fm->ipv4_report_created)
+ {
+ memset (a, 0, sizeof (*a));
+ a->rewrite_callback = flowperpkt_template_rewrite_ipv4;
+ a->flow_data_callback = flowperpkt_data_callback_ipv4;
+ a->is_add = 1;
+ a->domain_id = 1; /*$$$$ config parameter */
+ a->src_port = 4739; /*$$$$ config parameter */
+ fm->ipv4_report_created = 1;
+
+ rv = vnet_flow_report_add_del (frm, a);
+ if (rv)
+ {
+ clib_warning ("vnet_flow_report_add_del returned %d", rv);
+ return -1;
+ }
+ }
+ else if (which == FLOW_VARIANT_L2 && !fm->l2_report_created)
+ {
+ memset (a, 0, sizeof (*a));
+ a->rewrite_callback = flowperpkt_template_rewrite_l2;
+ a->flow_data_callback = flowperpkt_data_callback_l2;
+ a->is_add = 1;
+ a->domain_id = 1; /*$$$$ config parameter */
+ a->src_port = 4739; /*$$$$ config parameter */
+ fm->l2_report_created = 1;
+
+ rv = vnet_flow_report_add_del (frm, a);
+ if (rv)
+ {
+ clib_warning ("vnet_flow_report_add_del returned %d", rv);
+ return -1;
+ }
+ }
+
+ if (which == FLOW_VARIANT_IPV4)
+ vnet_feature_enable_disable ("ip4-output", "flowperpkt-ipv4",
+ sw_if_index, is_add, 0, 0);
+ else if (which == FLOW_VARIANT_L2)
+ vnet_feature_enable_disable ("interface-output", "flowperpkt-l2",
+ sw_if_index, is_add, 0, 0);
+
+ return 0;
+}
+
+/**
+ * @brief API message handler
+ * @param mp vl_api_flowperpkt_tx_interface_add_del_t * mp the api message
+ */
+void vl_api_flowperpkt_tx_interface_add_del_t_handler
+ (vl_api_flowperpkt_tx_interface_add_del_t * mp)
+{
+ flowperpkt_main_t *fm = &flowperpkt_main;
+ vl_api_flowperpkt_tx_interface_add_del_reply_t *rmp;
+ u32 sw_if_index = ntohl (mp->sw_if_index);
+ int rv = 0;
+
+ VALIDATE_SW_IF_INDEX (mp);
+
+ if (mp->which != FLOW_VARIANT_IPV4 && mp->which != FLOW_VARIANT_L2)
+ {
+ rv = VNET_API_ERROR_UNIMPLEMENTED;
+ goto out;
+ }
+
+ rv = flowperpkt_tx_interface_add_del_feature (fm, sw_if_index, mp->which,
+ mp->is_add);
+out:
+ BAD_SW_IF_INDEX_LABEL;
+
+ REPLY_MACRO (VL_API_FLOWPERPKT_TX_INTERFACE_ADD_DEL_REPLY);
+}
+
+/**
+ * @brief API message custom-dump function
+ * @param mp vl_api_flowperpkt_tx_interface_add_del_t * mp the api message
+ * @param handle void * print function handle
+ * @returns u8 * output string
+ */
+static void *vl_api_flowperpkt_tx_interface_add_del_t_print
+ (vl_api_flowperpkt_tx_interface_add_del_t * mp, void *handle)
+{
+ u8 *s;
+
+ s = format (0, "SCRIPT: flowperpkt_tx_interface_add_del ");
+ s = format (s, "sw_if_index %d is_add %d which %d ",
+ clib_host_to_net_u32 (mp->sw_if_index),
+ (int) mp->is_add, (int) mp->which);
+ FINISH;
+}
+
+/* List of message types that this plugin understands */
+#define foreach_flowperpkt_plugin_api_msg \
+_(FLOWPERPKT_TX_INTERFACE_ADD_DEL, flowperpkt_tx_interface_add_del)
+
+/**
+ * @brief plugin-api required function
+ * @param vm vlib_main_t * vlib main data structure pointer
+ * @param h vlib_plugin_handoff_t * handoff structure
+ * @param from_early_init int notused
+ *
+ * <em>Notes:</em>
+ * This routine exists to convince the vlib plugin framework that
+ * we haven't accidentally copied a random .dll into the plugin directory.
+ *
+ * Also collects global variable pointers passed from the vpp engine
+ */
+clib_error_t *
+vlib_plugin_register (vlib_main_t * vm, vnet_plugin_handoff_t * h,
+ int from_early_init)
+{
+ flowperpkt_main_t *fm = &flowperpkt_main;
+ clib_error_t *error = 0;
+
+ fm->vlib_main = vm;
+ fm->vnet_main = h->vnet_main;
+
+ return error;
+}
+
+static clib_error_t *
+flowperpkt_tx_interface_add_del_feature_command_fn (vlib_main_t * vm,
+ unformat_input_t * input,
+ vlib_cli_command_t * cmd)
+{
+ flowperpkt_main_t *fm = &flowperpkt_main;
+ u32 sw_if_index = ~0;
+ int is_add = 1;
+ u8 which = FLOW_VARIANT_IPV4;
+
+ int rv;
+
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "disable"))
+ is_add = 0;
+ else if (unformat (input, "%U", unformat_vnet_sw_interface,
+ fm->vnet_main, &sw_if_index));
+ else if (unformat (input, "l2"))
+ which = FLOW_VARIANT_L2;
+ else
+ break;
+ }
+
+ if (sw_if_index == ~0)
+ return clib_error_return (0, "Please specify an interface...");
+
+ rv =
+ flowperpkt_tx_interface_add_del_feature (fm, sw_if_index, which, is_add);
+ switch (rv)
+ {
+ case 0:
+ break;
+
+ case VNET_API_ERROR_INVALID_SW_IF_INDEX:
+ return clib_error_return
+ (0, "Invalid interface, only works on physical ports");
+ break;
+
+ case VNET_API_ERROR_UNIMPLEMENTED:
+ return clib_error_return (0, "ip6 not supported");
+ break;
+
+ default:
+ return clib_error_return (0, "flowperpkt_enable_disable returned %d",
+ rv);
+ }
+ return 0;
+}
+
+/*?
+ * '<em>flowperpkt feature add-del</em>' commands to enable/disable
+ * per-packet IPFIX flow record generation on an interface
+ *
+ * @cliexpar
+ * @parblock
+ * To enable per-packet IPFIX flow-record generation on an interface:
+ * @cliexcmd{flowperpkt feature add-del GigabitEthernet2/0/0}
+ *
+ * To disable per-packet IPFIX flow-record generation on an interface:
+ * @cliexcmd{flowperpkt feature add-del GigabitEthernet2/0/0 disable}
+ * @cliexend
+ * @endparblock
+?*/
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (flowperpkt_enable_disable_command, static) = {
+ .path = "flowperpkt feature add-del",
+ .short_help =
+ "flowperpkt feature add-del <interface-name> [disable]",
+ .function = flowperpkt_tx_interface_add_del_feature_command_fn,
+};
+/* *INDENT-ON* */
+
+/**
+ * @brief Set up the API message handling tables
+ * @param vm vlib_main_t * vlib main data structure pointer
+ * @returns 0 to indicate all is well
+ */
+static clib_error_t *
+flowperpkt_plugin_api_hookup (vlib_main_t * vm)
+{
+ flowperpkt_main_t *fm = &flowperpkt_main;
+#define _(N,n) \
+ vl_msg_api_set_handlers((VL_API_##N + fm->msg_id_base), \
+ #n, \
+ vl_api_##n##_t_handler, \
+ vl_noop_handler, \
+ vl_api_##n##_t_endian, \
+ vl_api_##n##_t_print, \
+ sizeof(vl_api_##n##_t), 1);
+ foreach_flowperpkt_plugin_api_msg;
+#undef _
+
+ return 0;
+}
+
+#define vl_msg_name_crc_list
+#include <flowperpkt/flowperpkt_all_api_h.h>
+#undef vl_msg_name_crc_list
+
+static void
+setup_message_id_table (flowperpkt_main_t * fm, api_main_t * am)
+{
+#define _(id,n,crc) \
+ vl_msg_api_add_msg_name_crc (am, #n "_" #crc, id + fm->msg_id_base);
+ foreach_vl_msg_name_crc_flowperpkt;
+#undef _
+}
+
+/**
+ * @brief Set up the API message handling tables
+ * @param vm vlib_main_t * vlib main data structure pointer
+ * @returns 0 to indicate all is well, or a clib_error_t
+ */
+static clib_error_t *
+flowperpkt_init (vlib_main_t * vm)
+{
+ flowperpkt_main_t *fm = &flowperpkt_main;
+ vlib_thread_main_t *tm = &vlib_thread_main;
+ clib_error_t *error = 0;
+ u32 num_threads;
+ u8 *name;
+
+ /* Construct the API name */
+ name = format (0, "flowperpkt_%08x%c", api_version, 0);
+
+ /* Ask for a correctly-sized block of API message decode slots */
+ fm->msg_id_base = vl_msg_api_get_msg_ids
+ ((char *) name, VL_MSG_FIRST_AVAILABLE);
+
+ /* Hook up message handlers */
+ error = flowperpkt_plugin_api_hookup (vm);
+
+ /* Add our API messages to the global name_crc hash table */
+ setup_message_id_table (fm, &api_main);
+
+ vec_free (name);
+
+ /* Decide how many worker threads we have */
+ num_threads = 1 /* main thread */ + tm->n_eal_threads;
+
+ /* Allocate per worker thread vectors */
+ vec_validate (fm->ipv4_buffers_per_worker, num_threads - 1);
+ vec_validate (fm->l2_buffers_per_worker, num_threads - 1);
+ vec_validate (fm->ipv4_frames_per_worker, num_threads - 1);
+ vec_validate (fm->l2_frames_per_worker, num_threads - 1);
+ vec_validate (fm->ipv4_next_record_offset_per_worker, num_threads - 1);
+ vec_validate (fm->l2_next_record_offset_per_worker, num_threads - 1);
+
+ /* Set up time reference pair */
+ fm->vlib_time_0 = vlib_time_now (vm);
+ fm->nanosecond_time_0 = unix_time_now_nsec ();
+
+ return error;
+}
+
+VLIB_INIT_FUNCTION (flowperpkt_init);
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */