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