| .. _ipfix_doc: |
| |
| IPFIX support |
| ============= |
| |
| VPP includes a high-performance IPFIX record exporter. This note |
| explains how to use the internal APIs to export IPFIX data, and how to |
| configure and send the required IPFIX templates. |
| |
| As you’ll see, a bit of typing is required. |
| |
| First: create an ipfix “report” |
| ------------------------------- |
| |
| Include the flow report header file, fill out a @ref |
| vnet_flow_report_add_del_args_t structure, and call |
| vnet_flow_report_add_del. |
| |
| .. code:: c |
| |
| #include <vnet/ipfix-export/flow_report.h> |
| /* Defined in flow_report.h, of interest when constructing reports */ |
| |
| /* ipfix field definitions for a particular report */ |
| typedef struct |
| { |
| u32 info_element; |
| u32 size; |
| } ipfix_report_element_t; |
| |
| /* Report add/del argument structure */ |
| typedef struct |
| { |
| /* Callback to flush current ipfix packet / frame */ |
| vnet_flow_data_callback_t *flow_data_callback; |
| |
| /* Callback to build the template packet rewrite string */ |
| vnet_flow_rewrite_callback_t *rewrite_callback; |
| |
| /* List of ipfix elements in the report */ |
| ipfix_report_element_t *report_elements; |
| u32 n_report_elements; |
| /* Kept in flow report, used e.g. by flow classifier */ |
| opaque_t opaque; |
| /* Add / delete a report */ |
| int is_add; |
| /* Ipfix "domain-ID", see RFC, set as desired */ |
| u32 domain_id; |
| /* ipfix packet source port, often set to UDP_DST_PORT_ipfix */ |
| u16 src_port; |
| /* Set by ipfix infra, needed to send data packets */ |
| u32 *stream_indexp; |
| } vnet_flow_report_add_del_args_t; |
| |
| /* Private header file contents */ |
| |
| /* Report ipfix element definition */ |
| #define foreach_simple_report_ipfix_element \ |
| _(sourceIPv4Address, 4) \ |
| _(destinationIPv4Address, 4) \ |
| _(sourceTransportPort, 2) \ |
| _(destinationTransportPort, 2) \ |
| _(protocolIdentifier, 1) \ |
| _(flowStartMicroseconds, 8) \ |
| _(flowEndMicroseconds, 8) |
| |
| static ipfix_report_element_t simple_report_elements[] = { |
| #define _(a,b) {a,b}, |
| foreach_simple_report_ipfix_element |
| #undef _ |
| }; |
| |
| typedef struct |
| { |
| /** Buffers and frames, per thread */ |
| vlib_buffer_t **buffers_by_thread; |
| vlib_frame_t **frames_by_thread; |
| u32 *next_record_offset_by_thread; |
| |
| /** Template ID's */ |
| u16 *template_ids; |
| |
| /** Time reference pair */ |
| u64 usec_time_0; |
| f64 vlib_time_0; |
| |
| /** Stream index */ |
| u32 stream_index; |
| |
| /* Convenience */ |
| flow_report_main_t *flow_report_main; |
| vlib_main_t *vlib_main; |
| vnet_main_t *vnet_main; |
| } my_logging_main_t; |
| |
| extern my_logging_main_t my_logging_main; |
| |
| ... |
| |
| /* Recitations */ |
| flow_report_main_t *frm = &flow_report_main; |
| my_logging_main_t *mlm = &my_logging_main; |
| vnet_flow_report_add_del_args_t a; |
| int rv; |
| u16 template_id; |
| |
| ... |
| |
| /* Init function: set up time reference pair */ |
| mlm->vlib_time_0 = vlib_time_now (vm); |
| mlm->milisecond_time_0 = unix_time_now_nsec () * 1e-6; |
| |
| ... |
| |
| /* Create a report */ |
| memset (&a, 0, sizeof (a)); |
| a.is_add = 1 /* to enable the report */; |
| a.domain_id = 1 /* pick a domain ID */; |
| a.src_port = UDP_DST_PORT_ipfix /* src port for reports */; |
| |
| /* Use the generic template packet rewrite string generator */ |
| a.rewrite_callback = vnet_flow_rewrite_generic_callback; |
| |
| /* Supply a list of ipfix report elements */ |
| a.report_elements = simple_report_elements; |
| a.n_report_elements = ARRAY_LEN (simple_report_elements); |
| |
| /* Pointer to the ipfix stream index, set by the report infra */ |
| a.stream_indexp = &mlm->stream_index; |
| a.flow_data_callback = my_flow_data_callback; |
| |
| /* Create the report */ |
| rv = vnet_flow_report_add_del (frm, &a, &template_id); |
| if (rv) |
| oops... |
| |
| /* Save the template-ID for later use */ |
| mlm->template_id = template_id; |
| |
| Several things are worth describing in more detail. |
| |
| vnet_flow_rewrite_generic_callback programming |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| |
| This generic callback helps build ipfix template packets. When |
| registering an ipfix report, pass an (array, count) of ipfix elements as |
| shown above. |
| |
| my_flow_data_callback |
| ~~~~~~~~~~~~~~~~~~~~~ |
| |
| The ipfix flow export infrastructure calls this callback to flush the |
| current ipfix packet; to make sure that ipfix data is not retained for |
| an unreasonably long period of time. |
| |
| We typically code it as shown below, to call an application-specific |
| function with (uninteresting arguments), and “do_flush = 1”: |
| |
| .. code:: c |
| |
| |
| vlib_frame_t *my_flow_data_callback |
| (flow_report_main_t * frm, |
| flow_report_t * fr, |
| vlib_frame_t * f, |
| u32 * to_next, u32 node_index) |
| { |
| |
| my_buffer_flow_record (0, ... , 0, 1 /* do_flush */); |
| return f; |
| } |
| |
| my_flow_data_header |
| ~~~~~~~~~~~~~~~~~~~ |
| |
| This function creates the packet header for an ipfix data packet |
| |
| .. code:: c |
| |
| |
| static inline void |
| my_flow_report_header (flow_report_main_t * frm, |
| vlib_buffer_t * b0, u32 * offset) |
| { |
| my_logging_main_t *mlm = &my_logging_main; |
| flow_report_stream_t *stream; |
| ip4_ipfix_template_packet_t *tp; |
| ipfix_message_header_t *h = 0; |
| |
| |
| ipfix_set_header_t *s = 0; |
| ip4_header_t *ip; |
| udp_header_t *udp; |
| |
| stream = &frm->streams[mlm->stream_index]; |
| |
| b0->current_data = 0; |
| b0->current_length = sizeof (*ip) + sizeof (*udp) + sizeof (*h) + |
| sizeof (*s); |
| b0->flags |= (VLIB_BUFFER_TOTAL_LENGTH_VALID | VNET_BUFFER_F_FLOW_REPORT); |
| vnet_buffer (b0)->sw_if_index[VLIB_RX] = 0; |
| vnet_buffer (b0)->sw_if_index[VLIB_TX] = frm->fib_index; |
| tp = vlib_buffer_get_current (b0); |
| 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); |
| |
| ip->ip_version_and_header_length = 0x45; |
| ip->ttl = 254; |
| ip->protocol = IP_PROTOCOL_UDP; |
| ip->flags_and_fragment_offset = 0; |
| ip->src_address.as_u32 = frm->src_address.as_u32; |
| ip->dst_address.as_u32 = frm->ipfix_collector.as_u32; |
| udp->src_port = clib_host_to_net_u16 (stream->src_port); |
| udp->dst_port = clib_host_to_net_u16 (frm->collector_port); |
| udp->checksum = 0; |
| |
| h->export_time = clib_host_to_net_u32 ((u32) |
| (((f64) frm->unix_time_0) + |
| (vlib_time_now (frm->vlib_main) - |
| frm->vlib_time_0))); |
| h->sequence_number = clib_host_to_net_u32 (stream->sequence_number++); |
| h->domain_id = clib_host_to_net_u32 (stream->domain_id); |
| |
| *offset = (u32) (((u8 *) (s + 1)) - (u8 *) tp); |
| } |
| |
| ### fixup and transmit a flow record |
| |
| .. code:: c |
| |
| |
| static inline void |
| my_send_ipfix_pkt (flow_report_main_t * frm, |
| vlib_frame_t * f, vlib_buffer_t * b0, u16 template_id) |
| { |
| ip4_ipfix_template_packet_t *tp; |
| ipfix_message_header_t *h = 0; |
| ipfix_set_header_t *s = 0; |
| ip4_header_t *ip; |
| udp_header_t *udp; |
| vlib_main_t *vm = frm->vlib_main; |
| |
| tp = vlib_buffer_get_current (b0); |
| 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); |
| |
| s->set_id_length = ipfix_set_id_length (template_id, |
| b0->current_length - |
| (sizeof (*ip) + sizeof (*udp) + |
| sizeof (*h))); |
| h->version_length = version_length (b0->current_length - |
| (sizeof (*ip) + sizeof (*udp))); |
| |
| ip->length = clib_host_to_net_u16 (b0->current_length); |
| ip->checksum = ip4_header_checksum (ip); |
| udp->length = clib_host_to_net_u16 (b0->current_length - sizeof (*ip)); |
| |
| if (frm->udp_checksum) |
| { |
| udp->checksum = ip4_tcp_udp_compute_checksum (vm, b0, ip); |
| if (udp->checksum == 0) |
| udp->checksum = 0xffff; |
| } |
| |
| ASSERT (ip4_header_checksum_is_valid (ip)); |
| |
| vlib_put_frame_to_node (vm, ip4_lookup_node.index, f); |
| } |
| |
| ### my_buffer_flow_record |
| |
| This is the key routine which paints individual flow records into an |
| ipfix packet under construction. It’s pretty straightforward (albeit |
| stateful) vpp data-plane code. The code shown below is thread-safe by |
| construction. |
| |
| .. code:: c |
| |
| static inline void |
| my_buffer_flow_record_internal (my_flow_record_t * rp, int do_flush, |
| u32 thread_index) |
| { |
| vlib_main_t *vm = vlib_mains[thread_index]; |
| my_logging_main_t *mlm = &jvp_ipfix_main; |
| flow_report_main_t *frm = &flow_report_main; |
| vlib_frame_t *f; |
| vlib_buffer_t *b0 = 0; |
| u32 bi0 = ~0; |
| u32 offset; |
| |
| b0 = mlm->buffers_by_thread[thread_index]; |
| |
| if (PREDICT_FALSE (b0 == 0)) |
| { |
| if (do_flush) |
| return; |
| |
| if (vlib_buffer_alloc (vm, &bi0, 1) != 1) |
| { |
| clib_warning ("can't allocate ipfix data buffer"); |
| return; |
| } |
| |
| b0 = vlib_get_buffer (vm, bi0); |
| offset = 0; |
| mlm->buffers_by_thread[thread_index] = b0; |
| } |
| else |
| { |
| bi0 = vlib_get_buffer_index (vm, b0); |
| offset = mlm->next_record_offset_by_thread[thread_index]; |
| } |
| |
| f = mlm->frames_by_thread[thread_index]; |
| if (PREDICT_FALSE (f == 0)) |
| { |
| u32 *to_next; |
| f = vlib_get_frame_to_node (vm, ip4_lookup_node.index); |
| mlm->frames_by_thread[thread_index] = f; |
| to_next = vlib_frame_vector_args (f); |
| to_next[0] = bi0; |
| f->n_vectors = 1; |
| mlm->frames_by_thread[thread_index] = f; |
| } |
| |
| if (PREDICT_FALSE (offset == 0)) |
| my_flow_report_header (frm, b0, &offset); |
| |
| if (PREDICT_TRUE (do_flush == 0)) |
| { |
| /* Paint the new ipfix data record into the buffer */ |
| clib_memcpy (b0->data + offset, rp, sizeof (*rp)); |
| offset += sizeof (*rp); |
| b0->current_length += sizeof (*rp); |
| } |
| |
| if (PREDICT_FALSE (do_flush || (offset + sizeof (*rp)) > frm->path_mtu)) |
| { |
| /* Nothing to send? */ |
| if (offset == 0) |
| return; |
| |
| send_ipfix_pkt (frm, f, b0, mlm->template_ids[0]); |
| mlm->buffers_by_thread[thread_index] = 0; |
| mlm->frames_by_thread[thread_index] = 0; |
| offset = 0; |
| } |
| mlm->next_record_offset_by_thread[thread_index] = offset; |
| } |
| |
| static void |
| my_buffer_flow_record (my_flow_record_t * rp, int do_flush) |
| { |
| u32 thread_index = vlib_get_thread_index(); |
| my_buffer_flow_record_internal (rp, do_flush, thread_index); |
| } |