Reorganize source tree to use single autotools instance
[vpp.git] / src / plugins / flowperpkt / flowperpkt.c
1 /*
2  * flowperpkt.c - per-packet data capture flow report plugin
3  *
4  * Copyright (c) <current-year> <your-organization>
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at:
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 /**
19  * @file
20  * @brief Per-packet IPFIX flow record generator plugin
21  *
22  * This file implements vpp plugin registration mechanics,
23  * debug CLI, and binary API handling.
24  */
25
26 #include <vnet/vnet.h>
27 #include <vnet/plugin/plugin.h>
28 #include <flowperpkt/flowperpkt.h>
29
30 #include <vlibapi/api.h>
31 #include <vlibmemory/api.h>
32 #include <vlibsocket/api.h>
33
34 /* define message IDs */
35 #include <flowperpkt/flowperpkt_msg_enum.h>
36
37 /* define message structures */
38 #define vl_typedefs
39 #include <flowperpkt/flowperpkt_all_api_h.h>
40 #undef vl_typedefs
41
42 /* define generated endian-swappers */
43 #define vl_endianfun
44 #include <flowperpkt/flowperpkt_all_api_h.h>
45 #undef vl_endianfun
46
47 /* instantiate all the print functions we know about */
48 #define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
49 #define vl_printfun
50 #include <flowperpkt/flowperpkt_all_api_h.h>
51 #undef vl_printfun
52
53 flowperpkt_main_t flowperpkt_main;
54
55 /* Get the API version number */
56 #define vl_api_version(n,v) static u32 api_version=(v);
57 #include <flowperpkt/flowperpkt_all_api_h.h>
58 #undef vl_api_version
59
60 /* Define the per-interface configurable features */
61 /* *INDENT-OFF* */
62 VNET_FEATURE_INIT (flow_perpacket_ipv4, static) =
63 {
64   .arc_name = "ip4-output",
65   .node_name = "flowperpkt-ipv4",
66   .runs_before = VNET_FEATURES ("interface-output"),
67 };
68
69 VNET_FEATURE_INIT (flow_perpacket_l2, static) =
70 {
71   .arc_name = "interface-output",
72   .node_name = "flowperpkt-l2",
73   .runs_before = VNET_FEATURES ("interface-tx"),
74 };
75 /* *INDENT-ON* */
76
77 /*
78  * A handy macro to set up a message reply.
79  * Assumes that the following variables are available:
80  * mp - pointer to request message
81  * rmp - pointer to reply message type
82  * rv - return value
83  */
84 #define REPLY_MACRO(t)                                          \
85 do {                                                            \
86     unix_shared_memory_queue_t * q =                            \
87     vl_api_client_index_to_input_queue (mp->client_index);      \
88     if (!q)                                                     \
89         return;                                                 \
90                                                                 \
91     rmp = vl_msg_api_alloc (sizeof (*rmp));                     \
92     rmp->_vl_msg_id = ntohs((t)+fm->msg_id_base);               \
93     rmp->context = mp->context;                                 \
94     rmp->retval = ntohl(rv);                                    \
95                                                                 \
96     vl_msg_api_send_shmem (q, (u8 *)&rmp);                      \
97 } while(0);
98
99 /* Macro to finish up custom dump fns */
100 #define FINISH                                  \
101     vec_add1 (s, 0);                            \
102     vl_print (handle, (char *)s);               \
103     vec_free (s);                               \
104     return handle;
105
106 #define VALIDATE_SW_IF_INDEX(mp)                                \
107  do { u32 __sw_if_index = ntohl(mp->sw_if_index);               \
108     vnet_main_t *__vnm = vnet_get_main();                       \
109     if (pool_is_free_index(__vnm->interface_main.sw_interfaces, \
110                            __sw_if_index)) {                    \
111         rv = VNET_API_ERROR_INVALID_SW_IF_INDEX;                \
112         goto bad_sw_if_index;                                   \
113     }                                                           \
114 } while(0);
115
116 #define BAD_SW_IF_INDEX_LABEL                   \
117 do {                                            \
118 bad_sw_if_index:                                \
119     ;                                           \
120 } while (0);
121
122 /**
123  * @brief Create an IPFIX template packet rewrite string
124  * @param frm flow_report_main_t *
125  * @param fr flow_report_t *
126  * @param collector_address ip4_address_t * the IPFIX collector address
127  * @param src_address ip4_address_t * the source address we should use
128  * @param collector_port u16 the collector port we should use, host byte order
129  * @returns u8 * vector containing the indicated IPFIX template packet
130  */
131 static inline u8 *
132 flowperpkt_template_rewrite_inline (flow_report_main_t * frm,
133                                     flow_report_t * fr,
134                                     ip4_address_t * collector_address,
135                                     ip4_address_t * src_address,
136                                     u16 collector_port, int variant)
137 {
138   ip4_header_t *ip;
139   udp_header_t *udp;
140   ipfix_message_header_t *h;
141   ipfix_set_header_t *s;
142   ipfix_template_header_t *t;
143   ipfix_field_specifier_t *f;
144   ipfix_field_specifier_t *first_field;
145   u8 *rewrite = 0;
146   ip4_ipfix_template_packet_t *tp;
147   u32 field_count = 0;
148   flow_report_stream_t *stream;
149   flowperpkt_main_t *fm = &flowperpkt_main;
150
151   stream = &frm->streams[fr->stream_index];
152
153   if (variant == FLOW_VARIANT_IPV4)
154     {
155       /*
156        * ip4 Supported Fields:
157        *
158        * ingressInterface, TLV type 10, u32
159        * egressInterface, TLV type 14, u32
160        * sourceIpv4Address, TLV type 8, u32
161        * destinationIPv4Address, TLV type 12, u32
162        * ipClassOfService, TLV type 5, u8
163        * flowStartNanoseconds, TLV type 156, dateTimeNanoseconds (f64)
164        *   Implementation: f64 nanoseconds since VPP started
165        *   warning: wireshark doesn't really understand this TLV
166        * dataLinkFrameSize, TLV type 312, u16
167        *   warning: wireshark doesn't understand this TLV at all
168        */
169
170       /* Currently 7 fields */
171       field_count += 7;
172
173       /* allocate rewrite space */
174       vec_validate_aligned
175         (rewrite,
176          sizeof (ip4_ipfix_template_packet_t)
177          + field_count * sizeof (ipfix_field_specifier_t) - 1,
178          CLIB_CACHE_LINE_BYTES);
179     }
180   else if (variant == FLOW_VARIANT_L2)
181     {
182       /*
183        * L2 Supported Fields:
184        *
185        * ingressInterface, TLV type 10, u32
186        * egressInterface, TLV type 14, u32
187        * sourceMacAddress, TLV type 56, u8[6] we hope
188        * destinationMacAddress, TLV type 57, u8[6] we hope
189        * ethernetType, TLV type 256, u16
190        * flowStartNanoseconds, TLV type 156, dateTimeNanoseconds (f64)
191        *   Implementation: f64 nanoseconds since VPP started
192        *   warning: wireshark doesn't really understand this TLV
193        * dataLinkFrameSize, TLV type 312, u16
194        *   warning: wireshark doesn't understand this TLV at all
195        */
196
197       /* Currently 7 fields */
198       field_count += 7;
199
200       /* allocate rewrite space */
201       vec_validate_aligned
202         (rewrite,
203          sizeof (ip4_ipfix_template_packet_t)
204          + field_count * sizeof (ipfix_field_specifier_t) - 1,
205          CLIB_CACHE_LINE_BYTES);
206     }
207
208   tp = (ip4_ipfix_template_packet_t *) rewrite;
209   ip = (ip4_header_t *) & tp->ip4;
210   udp = (udp_header_t *) (ip + 1);
211   h = (ipfix_message_header_t *) (udp + 1);
212   s = (ipfix_set_header_t *) (h + 1);
213   t = (ipfix_template_header_t *) (s + 1);
214   first_field = f = (ipfix_field_specifier_t *) (t + 1);
215
216   ip->ip_version_and_header_length = 0x45;
217   ip->ttl = 254;
218   ip->protocol = IP_PROTOCOL_UDP;
219   ip->src_address.as_u32 = src_address->as_u32;
220   ip->dst_address.as_u32 = collector_address->as_u32;
221   udp->src_port = clib_host_to_net_u16 (stream->src_port);
222   udp->dst_port = clib_host_to_net_u16 (collector_port);
223   udp->length = clib_host_to_net_u16 (vec_len (rewrite) - sizeof (*ip));
224
225   /* FIXUP: message header export_time */
226   /* FIXUP: message header sequence_number */
227   h->domain_id = clib_host_to_net_u32 (stream->domain_id);
228
229   /* Add TLVs to the template */
230   if (variant == FLOW_VARIANT_IPV4)
231     {
232       f->e_id_length =
233         ipfix_e_id_length (0 /* enterprise */ , ingressInterface,
234                            4);
235       f++;
236       f->e_id_length =
237         ipfix_e_id_length (0 /* enterprise */ , egressInterface,
238                            4);
239       f++;
240       f->e_id_length =
241         ipfix_e_id_length (0 /* enterprise */ , sourceIPv4Address,
242                            4);
243       f++;
244       f->e_id_length =
245         ipfix_e_id_length (0 /* enterprise */ , destinationIPv4Address, 4);
246       f++;
247       f->e_id_length =
248         ipfix_e_id_length (0 /* enterprise */ , ipClassOfService,
249                            1);
250       f++;
251       f->e_id_length =
252         ipfix_e_id_length (0 /* enterprise */ , flowStartNanoseconds,
253                            8);
254       f++;
255       f->e_id_length =
256         ipfix_e_id_length (0 /* enterprise */ , dataLinkFrameSize,
257                            2);
258       f++;
259     }
260   else if (variant == FLOW_VARIANT_L2)
261     {
262       f->e_id_length =
263         ipfix_e_id_length (0 /* enterprise */ , ingressInterface,
264                            4);
265       f++;
266       f->e_id_length =
267         ipfix_e_id_length (0 /* enterprise */ , egressInterface,
268                            4);
269       f++;
270       f->e_id_length =
271         ipfix_e_id_length (0 /* enterprise */ , sourceMacAddress,
272                            6);
273       f++;
274       f->e_id_length =
275         ipfix_e_id_length (0 /* enterprise */ , destinationMacAddress, 6);
276       f++;
277       f->e_id_length = ipfix_e_id_length (0 /* enterprise */ , ethernetType,
278                                           2);
279       f++;
280       f->e_id_length =
281         ipfix_e_id_length (0 /* enterprise */ , flowStartNanoseconds,
282                            8);
283       f++;
284       f->e_id_length =
285         ipfix_e_id_length (0 /* enterprise */ , dataLinkFrameSize,
286                            2);
287       f++;
288     }
289
290   /* Extend in the obvious way, right here... */
291
292   /* Back to the template packet... */
293   ip = (ip4_header_t *) & tp->ip4;
294   udp = (udp_header_t *) (ip + 1);
295
296   ASSERT (f - first_field);
297   /* Field count in this template */
298   t->id_count = ipfix_id_count (fr->template_id, f - first_field);
299
300   if (variant == FLOW_VARIANT_IPV4)
301     fm->ipv4_report_id = fr->template_id;
302   else if (variant == FLOW_VARIANT_L2)
303     fm->l2_report_id = fr->template_id;
304
305   /* set length in octets */
306   s->set_id_length =
307     ipfix_set_id_length (2 /* set_id */ , (u8 *) f - (u8 *) s);
308
309   /* message length in octets */
310   h->version_length = version_length ((u8 *) f - (u8 *) h);
311
312   ip->length = clib_host_to_net_u16 ((u8 *) f - (u8 *) ip);
313   ip->checksum = ip4_header_checksum (ip);
314
315   return rewrite;
316 }
317
318 u8 *
319 flowperpkt_template_rewrite_ipv4 (flow_report_main_t * frm,
320                                   flow_report_t * fr,
321                                   ip4_address_t * collector_address,
322                                   ip4_address_t * src_address,
323                                   u16 collector_port)
324 {
325   return flowperpkt_template_rewrite_inline
326     (frm, fr, collector_address, src_address, collector_port,
327      FLOW_VARIANT_IPV4);
328 }
329
330 u8 *
331 flowperpkt_template_rewrite_l2 (flow_report_main_t * frm,
332                                 flow_report_t * fr,
333                                 ip4_address_t * collector_address,
334                                 ip4_address_t * src_address,
335                                 u16 collector_port)
336 {
337   return flowperpkt_template_rewrite_inline
338     (frm, fr, collector_address, src_address, collector_port,
339      FLOW_VARIANT_L2);
340 }
341
342
343 /**
344  * @brief Flush accumulated data
345  * @param frm flow_report_main_t *
346  * @param fr flow_report_t *
347  * @param f vlib_frame_t *
348  *
349  * <em>Notes:</em>
350  * This function must simply return the incoming frame, or no template packets
351  * will be sent.
352  */
353 vlib_frame_t *
354 flowperpkt_data_callback_ipv4 (flow_report_main_t * frm,
355                                flow_report_t * fr,
356                                vlib_frame_t * f, u32 * to_next,
357                                u32 node_index)
358 {
359   flowperpkt_flush_callback_ipv4 ();
360   return f;
361 }
362
363 vlib_frame_t *
364 flowperpkt_data_callback_l2 (flow_report_main_t * frm,
365                              flow_report_t * fr,
366                              vlib_frame_t * f, u32 * to_next, u32 node_index)
367 {
368   flowperpkt_flush_callback_l2 ();
369   return f;
370 }
371
372 /**
373  * @brief configure / deconfigure the IPFIX flow-per-packet
374  * @param fm flowperpkt_main_t * fm
375  * @param sw_if_index u32 the desired interface
376  * @param is_add int 1 to enable the feature, 0 to disable it
377  * @returns 0 if successful, non-zero otherwise
378  */
379
380 static int flowperpkt_tx_interface_add_del_feature
381   (flowperpkt_main_t * fm, u32 sw_if_index, int which, int is_add)
382 {
383   flow_report_main_t *frm = &flow_report_main;
384   vnet_flow_report_add_del_args_t _a, *a = &_a;
385   int rv;
386
387   if (which == FLOW_VARIANT_IPV4 && !fm->ipv4_report_created)
388     {
389       memset (a, 0, sizeof (*a));
390       a->rewrite_callback = flowperpkt_template_rewrite_ipv4;
391       a->flow_data_callback = flowperpkt_data_callback_ipv4;
392       a->is_add = 1;
393       a->domain_id = 1;         /*$$$$ config parameter */
394       a->src_port = 4739;       /*$$$$ config parameter */
395       fm->ipv4_report_created = 1;
396
397       rv = vnet_flow_report_add_del (frm, a);
398       if (rv)
399         {
400           clib_warning ("vnet_flow_report_add_del returned %d", rv);
401           return -1;
402         }
403     }
404   else if (which == FLOW_VARIANT_L2 && !fm->l2_report_created)
405     {
406       memset (a, 0, sizeof (*a));
407       a->rewrite_callback = flowperpkt_template_rewrite_l2;
408       a->flow_data_callback = flowperpkt_data_callback_l2;
409       a->is_add = 1;
410       a->domain_id = 1;         /*$$$$ config parameter */
411       a->src_port = 4739;       /*$$$$ config parameter */
412       fm->l2_report_created = 1;
413
414       rv = vnet_flow_report_add_del (frm, a);
415       if (rv)
416         {
417           clib_warning ("vnet_flow_report_add_del returned %d", rv);
418           return -1;
419         }
420     }
421
422   if (which == FLOW_VARIANT_IPV4)
423     vnet_feature_enable_disable ("ip4-output", "flowperpkt-ipv4",
424                                  sw_if_index, is_add, 0, 0);
425   else if (which == FLOW_VARIANT_L2)
426     vnet_feature_enable_disable ("interface-output", "flowperpkt-l2",
427                                  sw_if_index, is_add, 0, 0);
428
429   return 0;
430 }
431
432 /**
433  * @brief API message handler
434  * @param mp vl_api_flowperpkt_tx_interface_add_del_t * mp the api message
435  */
436 void vl_api_flowperpkt_tx_interface_add_del_t_handler
437   (vl_api_flowperpkt_tx_interface_add_del_t * mp)
438 {
439   flowperpkt_main_t *fm = &flowperpkt_main;
440   vl_api_flowperpkt_tx_interface_add_del_reply_t *rmp;
441   u32 sw_if_index = ntohl (mp->sw_if_index);
442   int rv = 0;
443
444   VALIDATE_SW_IF_INDEX (mp);
445
446   if (mp->which != FLOW_VARIANT_IPV4 && mp->which != FLOW_VARIANT_L2)
447     {
448       rv = VNET_API_ERROR_UNIMPLEMENTED;
449       goto out;
450     }
451
452   rv = flowperpkt_tx_interface_add_del_feature (fm, sw_if_index, mp->which,
453                                                 mp->is_add);
454 out:
455   BAD_SW_IF_INDEX_LABEL;
456
457   REPLY_MACRO (VL_API_FLOWPERPKT_TX_INTERFACE_ADD_DEL_REPLY);
458 }
459
460 /**
461  * @brief API message custom-dump function
462  * @param mp vl_api_flowperpkt_tx_interface_add_del_t * mp the api message
463  * @param handle void * print function handle
464  * @returns u8 * output string
465  */
466 static void *vl_api_flowperpkt_tx_interface_add_del_t_print
467   (vl_api_flowperpkt_tx_interface_add_del_t * mp, void *handle)
468 {
469   u8 *s;
470
471   s = format (0, "SCRIPT: flowperpkt_tx_interface_add_del ");
472   s = format (s, "sw_if_index %d is_add %d which %d ",
473               clib_host_to_net_u32 (mp->sw_if_index),
474               (int) mp->is_add, (int) mp->which);
475   FINISH;
476 }
477
478 /* List of message types that this plugin understands */
479 #define foreach_flowperpkt_plugin_api_msg                           \
480 _(FLOWPERPKT_TX_INTERFACE_ADD_DEL, flowperpkt_tx_interface_add_del)
481
482 /**
483  * @brief plugin-api required function
484  * @param vm vlib_main_t * vlib main data structure pointer
485  * @param h vlib_plugin_handoff_t * handoff structure
486  * @param from_early_init int notused
487  *
488  * <em>Notes:</em>
489  * This routine exists to convince the vlib plugin framework that
490  * we haven't accidentally copied a random .dll into the plugin directory.
491  *
492  * Also collects global variable pointers passed from the vpp engine
493  */
494 clib_error_t *
495 vlib_plugin_register (vlib_main_t * vm, vnet_plugin_handoff_t * h,
496                       int from_early_init)
497 {
498   flowperpkt_main_t *fm = &flowperpkt_main;
499   clib_error_t *error = 0;
500
501   fm->vlib_main = vm;
502   fm->vnet_main = h->vnet_main;
503
504   return error;
505 }
506
507 static clib_error_t *
508 flowperpkt_tx_interface_add_del_feature_command_fn (vlib_main_t * vm,
509                                                     unformat_input_t * input,
510                                                     vlib_cli_command_t * cmd)
511 {
512   flowperpkt_main_t *fm = &flowperpkt_main;
513   u32 sw_if_index = ~0;
514   int is_add = 1;
515   u8 which = FLOW_VARIANT_IPV4;
516
517   int rv;
518
519   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
520     {
521       if (unformat (input, "disable"))
522         is_add = 0;
523       else if (unformat (input, "%U", unformat_vnet_sw_interface,
524                          fm->vnet_main, &sw_if_index));
525       else if (unformat (input, "l2"))
526         which = FLOW_VARIANT_L2;
527       else
528         break;
529     }
530
531   if (sw_if_index == ~0)
532     return clib_error_return (0, "Please specify an interface...");
533
534   rv =
535     flowperpkt_tx_interface_add_del_feature (fm, sw_if_index, which, is_add);
536   switch (rv)
537     {
538     case 0:
539       break;
540
541     case VNET_API_ERROR_INVALID_SW_IF_INDEX:
542       return clib_error_return
543         (0, "Invalid interface, only works on physical ports");
544       break;
545
546     case VNET_API_ERROR_UNIMPLEMENTED:
547       return clib_error_return (0, "ip6 not supported");
548       break;
549
550     default:
551       return clib_error_return (0, "flowperpkt_enable_disable returned %d",
552                                 rv);
553     }
554   return 0;
555 }
556
557 /*?
558  * '<em>flowperpkt feature add-del</em>' commands to enable/disable
559  * per-packet IPFIX flow record generation on an interface
560  *
561  * @cliexpar
562  * @parblock
563  * To enable per-packet IPFIX flow-record generation on an interface:
564  * @cliexcmd{flowperpkt feature add-del GigabitEthernet2/0/0}
565  *
566  * To disable per-packet IPFIX flow-record generation on an interface:
567  * @cliexcmd{flowperpkt feature add-del GigabitEthernet2/0/0 disable}
568  * @cliexend
569  * @endparblock
570 ?*/
571 /* *INDENT-OFF* */
572 VLIB_CLI_COMMAND (flowperpkt_enable_disable_command, static) = {
573     .path = "flowperpkt feature add-del",
574     .short_help =
575     "flowperpkt feature add-del <interface-name> [disable]",
576     .function = flowperpkt_tx_interface_add_del_feature_command_fn,
577 };
578 /* *INDENT-ON* */
579
580 /**
581  * @brief Set up the API message handling tables
582  * @param vm vlib_main_t * vlib main data structure pointer
583  * @returns 0 to indicate all is well
584  */
585 static clib_error_t *
586 flowperpkt_plugin_api_hookup (vlib_main_t * vm)
587 {
588   flowperpkt_main_t *fm = &flowperpkt_main;
589 #define _(N,n)                                                  \
590     vl_msg_api_set_handlers((VL_API_##N + fm->msg_id_base),     \
591                            #n,                                  \
592                            vl_api_##n##_t_handler,              \
593                            vl_noop_handler,                     \
594                            vl_api_##n##_t_endian,               \
595                            vl_api_##n##_t_print,                \
596                            sizeof(vl_api_##n##_t), 1);
597   foreach_flowperpkt_plugin_api_msg;
598 #undef _
599
600   return 0;
601 }
602
603 #define vl_msg_name_crc_list
604 #include <flowperpkt/flowperpkt_all_api_h.h>
605 #undef vl_msg_name_crc_list
606
607 static void
608 setup_message_id_table (flowperpkt_main_t * fm, api_main_t * am)
609 {
610 #define _(id,n,crc) \
611   vl_msg_api_add_msg_name_crc (am, #n "_" #crc, id + fm->msg_id_base);
612   foreach_vl_msg_name_crc_flowperpkt;
613 #undef _
614 }
615
616 /**
617  * @brief Set up the API message handling tables
618  * @param vm vlib_main_t * vlib main data structure pointer
619  * @returns 0 to indicate all is well, or a clib_error_t
620  */
621 static clib_error_t *
622 flowperpkt_init (vlib_main_t * vm)
623 {
624   flowperpkt_main_t *fm = &flowperpkt_main;
625   vlib_thread_main_t *tm = &vlib_thread_main;
626   clib_error_t *error = 0;
627   u32 num_threads;
628   u8 *name;
629
630   /* Construct the API name */
631   name = format (0, "flowperpkt_%08x%c", api_version, 0);
632
633   /* Ask for a correctly-sized block of API message decode slots */
634   fm->msg_id_base = vl_msg_api_get_msg_ids
635     ((char *) name, VL_MSG_FIRST_AVAILABLE);
636
637   /* Hook up message handlers */
638   error = flowperpkt_plugin_api_hookup (vm);
639
640   /* Add our API messages to the global name_crc hash table */
641   setup_message_id_table (fm, &api_main);
642
643   vec_free (name);
644
645   /* Decide how many worker threads we have */
646   num_threads = 1 /* main thread */  + tm->n_eal_threads;
647
648   /* Allocate per worker thread vectors */
649   vec_validate (fm->ipv4_buffers_per_worker, num_threads - 1);
650   vec_validate (fm->l2_buffers_per_worker, num_threads - 1);
651   vec_validate (fm->ipv4_frames_per_worker, num_threads - 1);
652   vec_validate (fm->l2_frames_per_worker, num_threads - 1);
653   vec_validate (fm->ipv4_next_record_offset_per_worker, num_threads - 1);
654   vec_validate (fm->l2_next_record_offset_per_worker, num_threads - 1);
655
656   /* Set up time reference pair */
657   fm->vlib_time_0 = vlib_time_now (vm);
658   fm->nanosecond_time_0 = unix_time_now_nsec ();
659
660   return error;
661 }
662
663 VLIB_INIT_FUNCTION (flowperpkt_init);
664
665 /*
666  * fd.io coding-style-patch-verification: ON
667  *
668  * Local Variables:
669  * eval: (c-set-style "gnu")
670  * End:
671  */