IPFIX support {#ipfix}

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.

   #include <vnet/ipfix-export/flow_report.h>

   typedef struct
   {
     vnet_flow_data_callback_t *flow_data_callback;
     vnet_flow_rewrite_callback_t *rewrite_callback;
     opaque_t opaque;
     int is_add;
     u32 domain_id;
     u16 src_port;
   } vnet_flow_report_add_del_args_t;

   ...

   flow_report_main_t *frm = &flow_report_main;
   vnet_flow_report_add_del_args_t a;
   int rv;
   u16 template_id;

   ... 

   /* Set up time reference pair */
   mlm->vlib_time_0 = vlib_time_now (vm);
   mlm->milisecond_time_0 = unix_time_now_nsec () * 1e-6;

   ...

   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 */;
   a.rewrite_callback = my_template_packet_rewrite_callback;
   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 functions are worth describing in detail.

template packet rewrite callback function

This callback helps build ipfix template packets when required. We should reduce the amount of cut-'n-paste coding, since only a fraction of the code has anything to do with the specific ipfix template we're trying to build.

   u8 *
   my_template_packet_rewrite_callback (flow_report_main_t * frm,
                                        flow_report_t * fr,
				        ip4_address_t * collector_address,
				        ip4_address_t * src_address,
				        u16 collector_port)
   {
       my_logging_main_t *mlm = &my_logging_main; /* typical */
       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;

       stream = &frm->streams[fr->stream_index];

       field_count = number_of_fields_to_export;

       /* 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);

       /* create the packet rewrite string */
       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 LATER: message header export_time */
       h->domain_id = clib_host_to_net_u32 (stream->domain_id);

       /* 
        * Add your favorite info elements to the template. See
        * .../src/vnet/ipfix-export/ipfix_info_elements.h
        *
        * Highly advisable to make sure field count is correct!
        */

       f->e_id_length = ipfix_e_id_length (0, sourceIPv6Address, 16);
       f++;
       f->e_id_length = ipfix_e_id_length (0, postNATSourceIPv4Address, 4);
       f++;

       /* 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);

       /* 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;
   }      

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":


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


static inline void my_flow_report_header (flow_report_main_t * frm, vlib_buffer_t * b0, u32 * offset) { snat_ipfix_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

   
   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 (ip->checksum == ip4_header_checksum (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.

   static void
   my_buffer_flow_record (u32 datum0, u32 datum1, ..., int do_flush)
   {
     my_logging_main_t *mlm = &my_logging_main;
     flow_report_main_t *frm = &flow_report_main;
     vlib_frame_t *f;
     vlib_buffer_t *b0 = 0;
     u32 bi0 = ~0;
     u32 offset;
     vlib_main_t *vm = frm->vlib_main;
     u64 now;
     vlib_buffer_free_list_t *fl;
     my_flow_record_t my_flow_record;

     if (!mlm->enabled)
       return;

     now = (u64) ((vlib_time_now (vm) - silm->vlib_time_0) * 1e3);
     now += mlm->milisecond_time_0;

     /* 
      * (maybe) set up a packed structure from datum0...datumN 
      * Otherwise, paint directly into the buffer below...
      */
     my_flow_record.xxx = datum0;
     my_flow_record.yyy = datum1;


     b0 = mlm->my_data_buffer;

     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 = mlm->my_data_buffer = vlib_get_buffer (vm, bi0);
         fl =
   	vlib_buffer_get_free_list (vm, VLIB_BUFFER_DEFAULT_FREE_LIST_INDEX);
         vlib_buffer_init_for_free_list (b0, fl);
         VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b0);
         offset = 0;
       }
     else
       {
         bi0 = vlib_get_buffer_index (vm, b0);
         offset = mlm->my_next_record_offset;
       }

     f = mlm->my_ipfix_frame;
     if (PREDICT_FALSE (f == 0))
       {
         u32 *to_next;
         f = vlib_get_frame_to_node (vm, ip4_lookup_node.index);
         mlm->my_ipfix_frame = f;
         to_next = vlib_frame_vector_args (f);
         to_next[0] = bi0;
         f->n_vectors = 1;
       }

     if (PREDICT_FALSE (offset == 0))
       my_flow_report_header (frm, b0, &offset);

     if (PREDICT_TRUE (do_flush == 0))
       {
         /* paint time stamp into buffer */
         clib_memcpy (b0->data + offset, &time_stamp, sizeof (time_stamp));
         offset += sizeof (time_stamp);

         /* Paint the new ipfix data record into the buffer */
         clib_memcpy (b0->data + offset, &my_flow_record, 
                     sizeof (my_flow_record));
         offset += sizeof (my_flow_record);
         b0->current_length += sizeof(my_flow_record);
       }

     if (PREDICT_FALSE
         (do_flush || (offset + sizeof (my_flow_record)) > frm->path_mtu))
       {
         my_send_ipfix_pkt (frm, f, b0, mlm->template_id);
         mlm->my_ipfix_frame = 0;
         mlm->my_data_buffer = 0;
         offset = 0;
       }
     mlm->next_record_offset = offset;
   }