2 * flowperpkt.c - per-packet data capture flow report plugin
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:
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 * @brief Per-packet IPFIX flow record generator plugin
22 * This file implements vpp plugin registration mechanics,
23 * debug CLI, and binary API handling.
26 #include <vnet/vnet.h>
27 #include <vpp/app/version.h>
28 #include <vnet/plugin/plugin.h>
29 #include <flowperpkt/flowperpkt.h>
31 #include <vlibapi/api.h>
32 #include <vlibmemory/api.h>
33 #include <vlibsocket/api.h>
35 /* define message IDs */
36 #include <flowperpkt/flowperpkt_msg_enum.h>
38 /* define message structures */
40 #include <flowperpkt/flowperpkt_all_api_h.h>
43 /* define generated endian-swappers */
45 #include <flowperpkt/flowperpkt_all_api_h.h>
48 /* instantiate all the print functions we know about */
49 #define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
51 #include <flowperpkt/flowperpkt_all_api_h.h>
54 flowperpkt_main_t flowperpkt_main;
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>
61 /* Define the per-interface configurable features */
63 VNET_FEATURE_INIT (flow_perpacket_ipv4, static) =
65 .arc_name = "ip4-output",
66 .node_name = "flowperpkt-ipv4",
67 .runs_before = VNET_FEATURES ("interface-output"),
70 VNET_FEATURE_INIT (flow_perpacket_l2, static) =
72 .arc_name = "interface-output",
73 .node_name = "flowperpkt-l2",
74 .runs_before = VNET_FEATURES ("interface-tx"),
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
85 #define REPLY_MACRO(t) \
87 unix_shared_memory_queue_t * q = \
88 vl_api_client_index_to_input_queue (mp->client_index); \
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); \
97 vl_msg_api_send_shmem (q, (u8 *)&rmp); \
100 /* Macro to finish up custom dump fns */
103 vl_print (handle, (char *)s); \
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, \
112 rv = VNET_API_ERROR_INVALID_SW_IF_INDEX; \
113 goto bad_sw_if_index; \
117 #define BAD_SW_IF_INDEX_LABEL \
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
133 flowperpkt_template_rewrite_inline (flow_report_main_t * frm,
135 ip4_address_t * collector_address,
136 ip4_address_t * src_address,
137 u16 collector_port, int variant)
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;
147 ip4_ipfix_template_packet_t *tp;
149 flow_report_stream_t *stream;
150 flowperpkt_main_t *fm = &flowperpkt_main;
152 stream = &frm->streams[fr->stream_index];
154 if (variant == FLOW_VARIANT_IPV4)
157 * ip4 Supported Fields:
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
171 /* Currently 7 fields */
174 /* allocate rewrite space */
177 sizeof (ip4_ipfix_template_packet_t)
178 + field_count * sizeof (ipfix_field_specifier_t) - 1,
179 CLIB_CACHE_LINE_BYTES);
181 else if (variant == FLOW_VARIANT_L2)
184 * L2 Supported Fields:
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
198 /* Currently 7 fields */
201 /* allocate rewrite space */
204 sizeof (ip4_ipfix_template_packet_t)
205 + field_count * sizeof (ipfix_field_specifier_t) - 1,
206 CLIB_CACHE_LINE_BYTES);
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);
217 ip->ip_version_and_header_length = 0x45;
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));
226 /* FIXUP: message header export_time */
227 /* FIXUP: message header sequence_number */
228 h->domain_id = clib_host_to_net_u32 (stream->domain_id);
230 /* Add TLVs to the template */
231 if (variant == FLOW_VARIANT_IPV4)
234 ipfix_e_id_length (0 /* enterprise */ , ingressInterface,
238 ipfix_e_id_length (0 /* enterprise */ , egressInterface,
242 ipfix_e_id_length (0 /* enterprise */ , sourceIPv4Address,
246 ipfix_e_id_length (0 /* enterprise */ , destinationIPv4Address, 4);
249 ipfix_e_id_length (0 /* enterprise */ , ipClassOfService,
253 ipfix_e_id_length (0 /* enterprise */ , flowStartNanoseconds,
257 ipfix_e_id_length (0 /* enterprise */ , dataLinkFrameSize,
261 else if (variant == FLOW_VARIANT_L2)
264 ipfix_e_id_length (0 /* enterprise */ , ingressInterface,
268 ipfix_e_id_length (0 /* enterprise */ , egressInterface,
272 ipfix_e_id_length (0 /* enterprise */ , sourceMacAddress,
276 ipfix_e_id_length (0 /* enterprise */ , destinationMacAddress, 6);
278 f->e_id_length = ipfix_e_id_length (0 /* enterprise */ , ethernetType,
282 ipfix_e_id_length (0 /* enterprise */ , flowStartNanoseconds,
286 ipfix_e_id_length (0 /* enterprise */ , dataLinkFrameSize,
291 /* Extend in the obvious way, right here... */
293 /* Back to the template packet... */
294 ip = (ip4_header_t *) & tp->ip4;
295 udp = (udp_header_t *) (ip + 1);
297 ASSERT (f - first_field);
298 /* Field count in this template */
299 t->id_count = ipfix_id_count (fr->template_id, f - first_field);
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;
306 /* set length in octets */
308 ipfix_set_id_length (2 /* set_id */ , (u8 *) f - (u8 *) s);
310 /* message length in octets */
311 h->version_length = version_length ((u8 *) f - (u8 *) h);
313 ip->length = clib_host_to_net_u16 ((u8 *) f - (u8 *) ip);
314 ip->checksum = ip4_header_checksum (ip);
320 flowperpkt_template_rewrite_ipv4 (flow_report_main_t * frm,
322 ip4_address_t * collector_address,
323 ip4_address_t * src_address,
326 return flowperpkt_template_rewrite_inline
327 (frm, fr, collector_address, src_address, collector_port,
332 flowperpkt_template_rewrite_l2 (flow_report_main_t * frm,
334 ip4_address_t * collector_address,
335 ip4_address_t * src_address,
338 return flowperpkt_template_rewrite_inline
339 (frm, fr, collector_address, src_address, collector_port,
345 * @brief Flush accumulated data
346 * @param frm flow_report_main_t *
347 * @param fr flow_report_t *
348 * @param f vlib_frame_t *
351 * This function must simply return the incoming frame, or no template packets
355 flowperpkt_data_callback_ipv4 (flow_report_main_t * frm,
357 vlib_frame_t * f, u32 * to_next,
360 flowperpkt_flush_callback_ipv4 ();
365 flowperpkt_data_callback_l2 (flow_report_main_t * frm,
367 vlib_frame_t * f, u32 * to_next, u32 node_index)
369 flowperpkt_flush_callback_l2 ();
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
381 static int flowperpkt_tx_interface_add_del_feature
382 (flowperpkt_main_t * fm, u32 sw_if_index, int which, int is_add)
384 flow_report_main_t *frm = &flow_report_main;
385 vnet_flow_report_add_del_args_t _a, *a = &_a;
388 if (which == FLOW_VARIANT_IPV4 && !fm->ipv4_report_created)
390 memset (a, 0, sizeof (*a));
391 a->rewrite_callback = flowperpkt_template_rewrite_ipv4;
392 a->flow_data_callback = flowperpkt_data_callback_ipv4;
394 a->domain_id = 1; /*$$$$ config parameter */
395 a->src_port = 4739; /*$$$$ config parameter */
396 fm->ipv4_report_created = 1;
398 rv = vnet_flow_report_add_del (frm, a);
401 clib_warning ("vnet_flow_report_add_del returned %d", rv);
405 else if (which == FLOW_VARIANT_L2 && !fm->l2_report_created)
407 memset (a, 0, sizeof (*a));
408 a->rewrite_callback = flowperpkt_template_rewrite_l2;
409 a->flow_data_callback = flowperpkt_data_callback_l2;
411 a->domain_id = 1; /*$$$$ config parameter */
412 a->src_port = 4739; /*$$$$ config parameter */
413 fm->l2_report_created = 1;
415 rv = vnet_flow_report_add_del (frm, a);
418 clib_warning ("vnet_flow_report_add_del returned %d", rv);
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);
434 * @brief API message handler
435 * @param mp vl_api_flowperpkt_tx_interface_add_del_t * mp the api message
437 void vl_api_flowperpkt_tx_interface_add_del_t_handler
438 (vl_api_flowperpkt_tx_interface_add_del_t * mp)
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);
445 VALIDATE_SW_IF_INDEX (mp);
447 if (mp->which != FLOW_VARIANT_IPV4 && mp->which != FLOW_VARIANT_L2)
449 rv = VNET_API_ERROR_UNIMPLEMENTED;
453 rv = flowperpkt_tx_interface_add_del_feature (fm, sw_if_index, mp->which,
456 BAD_SW_IF_INDEX_LABEL;
458 REPLY_MACRO (VL_API_FLOWPERPKT_TX_INTERFACE_ADD_DEL_REPLY);
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
467 static void *vl_api_flowperpkt_tx_interface_add_del_t_print
468 (vl_api_flowperpkt_tx_interface_add_del_t * mp, void *handle)
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);
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)
484 VLIB_PLUGIN_REGISTER () = {
485 .version = VPP_BUILD_VER,
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)
494 flowperpkt_main_t *fm = &flowperpkt_main;
495 u32 sw_if_index = ~0;
497 u8 which = FLOW_VARIANT_IPV4;
501 while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
503 if (unformat (input, "disable"))
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;
513 if (sw_if_index == ~0)
514 return clib_error_return (0, "Please specify an interface...");
517 flowperpkt_tx_interface_add_del_feature (fm, sw_if_index, which, is_add);
523 case VNET_API_ERROR_INVALID_SW_IF_INDEX:
524 return clib_error_return
525 (0, "Invalid interface, only works on physical ports");
528 case VNET_API_ERROR_UNIMPLEMENTED:
529 return clib_error_return (0, "ip6 not supported");
533 return clib_error_return (0, "flowperpkt_enable_disable returned %d",
540 * '<em>flowperpkt feature add-del</em>' commands to enable/disable
541 * per-packet IPFIX flow record generation on an interface
545 * To enable per-packet IPFIX flow-record generation on an interface:
546 * @cliexcmd{flowperpkt feature add-del GigabitEthernet2/0/0}
548 * To disable per-packet IPFIX flow-record generation on an interface:
549 * @cliexcmd{flowperpkt feature add-del GigabitEthernet2/0/0 disable}
554 VLIB_CLI_COMMAND (flowperpkt_enable_disable_command, static) = {
555 .path = "flowperpkt feature add-del",
557 "flowperpkt feature add-del <interface-name> [disable]",
558 .function = flowperpkt_tx_interface_add_del_feature_command_fn,
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
567 static clib_error_t *
568 flowperpkt_plugin_api_hookup (vlib_main_t * vm)
570 flowperpkt_main_t *fm = &flowperpkt_main;
572 vl_msg_api_set_handlers((VL_API_##N + fm->msg_id_base), \
574 vl_api_##n##_t_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;
585 #define vl_msg_name_crc_list
586 #include <flowperpkt/flowperpkt_all_api_h.h>
587 #undef vl_msg_name_crc_list
590 setup_message_id_table (flowperpkt_main_t * fm, api_main_t * am)
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;
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
603 static clib_error_t *
604 flowperpkt_init (vlib_main_t * vm)
606 flowperpkt_main_t *fm = &flowperpkt_main;
607 vlib_thread_main_t *tm = &vlib_thread_main;
608 clib_error_t *error = 0;
612 fm->vnet_main = vnet_get_main ();
614 /* Construct the API name */
615 name = format (0, "flowperpkt_%08x%c", api_version, 0);
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);
621 /* Hook up message handlers */
622 error = flowperpkt_plugin_api_hookup (vm);
624 /* Add our API messages to the global name_crc hash table */
625 setup_message_id_table (fm, &api_main);
629 /* Decide how many worker threads we have */
630 num_threads = 1 /* main thread */ + tm->n_threads;
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);
640 /* Set up time reference pair */
641 fm->vlib_time_0 = vlib_time_now (vm);
642 fm->nanosecond_time_0 = unix_time_now_nsec ();
647 VLIB_INIT_FUNCTION (flowperpkt_init);
650 * fd.io coding-style-patch-verification: ON
653 * eval: (c-set-style "gnu")