Add ipfix exporter coding guide
[vpp.git] / src / vnet / ipfix-export / ipfix_doc.md
1 # IPFIX support {#ipfix}
2
3 VPP includes a high-performance IPFIX record exporter. This note
4 explains how to use the internal APIs to export IPFIX data, and how to
5 configure and send the required IPFIX templates.
6
7 As you'll see, a bit of typing is required. 
8
9 ## First: create an ipfix "report"
10
11 Include the flow report header file, fill out a @ref
12 vnet_flow_report_add_del_args_t structure, and call vnet_flow_report_add_del.
13
14 ```{.c}
15    #include <vnet/ipfix-export/flow_report.h>
16
17    typedef struct
18    {
19      vnet_flow_data_callback_t *flow_data_callback;
20      vnet_flow_rewrite_callback_t *rewrite_callback;
21      opaque_t opaque;
22      int is_add;
23      u32 domain_id;
24      u16 src_port;
25    } vnet_flow_report_add_del_args_t;
26
27    ...
28
29    flow_report_main_t *frm = &flow_report_main;
30    vnet_flow_report_add_del_args_t a;
31    int rv;
32    u16 template_id;
33
34    ... 
35
36    /* Set up time reference pair */
37    mlm->vlib_time_0 = vlib_time_now (vm);
38    mlm->milisecond_time_0 = unix_time_now_nsec () * 1e-6;
39
40    ...
41
42    memset (&a, 0, sizeof (a));
43    a.is_add = 1 /* to enable the report */;
44    a.domain_id = 1 /* pick a domain ID */;
45    a.src_port = UDP_DST_PORT_ipfix /* src port for reports */;
46    a.rewrite_callback = my_template_packet_rewrite_callback;
47    a.flow_data_callback = my_flow_data_callback;
48
49    /* Create the report */
50    rv = vnet_flow_report_add_del (frm, &a, &template_id);
51    if (rv) 
52      oops...
53
54    /* Save the template-ID for later use */
55    mlm->template_id = template_id;
56
57 ```
58
59 Several functions are worth describing in detail.
60
61 ### template packet rewrite callback function
62
63 This callback helps build ipfix template packets when required. We
64 should reduce the amount of cut-'n-paste coding, since only a fraction
65 of the code has anything to do with the specific ipfix template we're
66 trying to build.
67
68 ```{.c}
69    u8 *
70    my_template_packet_rewrite_callback (flow_report_main_t * frm,
71                                         flow_report_t * fr,
72                                         ip4_address_t * collector_address,
73                                         ip4_address_t * src_address,
74                                         u16 collector_port)
75    {
76        my_logging_main_t *mlm = &my_logging_main; /* typical */
77        ip4_header_t *ip;
78        udp_header_t *udp;
79        ipfix_message_header_t *h;
80        ipfix_set_header_t *s;
81        ipfix_template_header_t *t;
82        ipfix_field_specifier_t *f;
83        ipfix_field_specifier_t *first_field;
84        u8 *rewrite = 0;
85        ip4_ipfix_template_packet_t *tp;
86        u32 field_count = 0;
87        flow_report_stream_t *stream;
88
89        stream = &frm->streams[fr->stream_index];
90
91        field_count = number_of_fields_to_export;
92
93        /* allocate rewrite space */
94        vec_validate_aligned (rewrite,
95                         sizeof (ip4_ipfix_template_packet_t)
96                         + field_count * sizeof (ipfix_field_specifier_t) - 1,
97                         CLIB_CACHE_LINE_BYTES);
98
99        /* create the packet rewrite string */
100        tp = (ip4_ipfix_template_packet_t *) rewrite;
101        ip = (ip4_header_t *) & tp->ip4;
102        udp = (udp_header_t *) (ip + 1);
103        h = (ipfix_message_header_t *) (udp + 1);
104        s = (ipfix_set_header_t *) (h + 1);
105        t = (ipfix_template_header_t *) (s + 1);
106        first_field = f = (ipfix_field_specifier_t *) (t + 1);
107
108        ip->ip_version_and_header_length = 0x45;
109        ip->ttl = 254;
110        ip->protocol = IP_PROTOCOL_UDP;
111        ip->src_address.as_u32 = src_address->as_u32;
112        ip->dst_address.as_u32 = collector_address->as_u32;
113        udp->src_port = clib_host_to_net_u16 (stream->src_port);
114        udp->dst_port = clib_host_to_net_u16 (collector_port);
115        udp->length = clib_host_to_net_u16 (vec_len (rewrite) - sizeof (*ip));
116
117        /* FIXUP LATER: message header export_time */
118        h->domain_id = clib_host_to_net_u32 (stream->domain_id);
119
120        /* 
121         * Add your favorite info elements to the template. See
122         * .../src/vnet/ipfix-export/ipfix_info_elements.h
123         *
124         * Highly advisable to make sure field count is correct!
125         */
126
127        f->e_id_length = ipfix_e_id_length (0, sourceIPv6Address, 16);
128        f++;
129        f->e_id_length = ipfix_e_id_length (0, postNATSourceIPv4Address, 4);
130        f++;
131
132        /* Back to the template packet... */
133        ip = (ip4_header_t *) & tp->ip4;
134        udp = (udp_header_t *) (ip + 1);
135
136        ASSERT (f - first_field);
137        /* Field count in this template */
138        t->id_count = ipfix_id_count (fr->template_id, f - first_field);
139
140        /* set length in octets */
141        s->set_id_length =
142          ipfix_set_id_length (2 /* set_id */ , (u8 *) f - (u8 *) s);
143
144        /* message length in octets */
145        h->version_length = version_length ((u8 *) f - (u8 *) h);
146
147        ip->length = clib_host_to_net_u16 ((u8 *) f - (u8 *) ip);
148        ip->checksum = ip4_header_checksum (ip);
149
150        return rewrite;
151    }      
152 ```
153
154 ### my_flow_data_callback
155
156 The ipfix flow export infrastructure calls this callback to flush the
157 current ipfix packet; to make sure that ipfix data is not retained for
158 an unreasonably long period of time.
159
160 We typically code it as shown below, to call an application-specific
161 function with (uninteresting arguments), and "do_flush = 1":
162
163
164 ```{.c}
165
166       vlib_frame_t *my_flow_data_callback
167                    (flow_report_main_t * frm,
168                    flow_report_t * fr,
169                    vlib_frame_t * f,
170                    u32 * to_next, u32 node_index)
171       { 
172
173          my_buffer_flow_record (0, ... , 0, 1 /* do_flush */);
174          return f;
175       }
176 ```
177
178 ### my_flow_data_header
179
180 This function creates the packet header for an ipfix data packet
181
182 ```{.c}
183
184    static inline void
185    my_flow_report_header (flow_report_main_t * frm,
186                           vlib_buffer_t * b0, u32 * offset)
187    {
188       snat_ipfix_logging_main_t *mlm = &my_logging_main;
189       flow_report_stream_t *stream;
190       ip4_ipfix_template_packet_t *tp;
191       ipfix_message_header_t *h = 0;
192       ipfix_set_header_t *s = 0;
193       ip4_header_t *ip;
194       udp_header_t *udp;
195
196       stream = &frm->streams[mlm->stream_index];
197
198       b0->current_data = 0;
199       b0->current_length = sizeof (*ip) + sizeof (*udp) + sizeof (*h) +
200         sizeof (*s);
201       b0->flags |= (VLIB_BUFFER_TOTAL_LENGTH_VALID | VNET_BUFFER_F_FLOW_REPORT);
202       vnet_buffer (b0)->sw_if_index[VLIB_RX] = 0;
203       vnet_buffer (b0)->sw_if_index[VLIB_TX] = frm->fib_index;
204       tp = vlib_buffer_get_current (b0);
205       ip = (ip4_header_t *) & tp->ip4;
206       udp = (udp_header_t *) (ip + 1);
207       h = (ipfix_message_header_t *) (udp + 1);
208       s = (ipfix_set_header_t *) (h + 1);
209
210       ip->ip_version_and_header_length = 0x45;
211       ip->ttl = 254;
212       ip->protocol = IP_PROTOCOL_UDP;
213       ip->flags_and_fragment_offset = 0;
214       ip->src_address.as_u32 = frm->src_address.as_u32;
215       ip->dst_address.as_u32 = frm->ipfix_collector.as_u32;
216       udp->src_port = clib_host_to_net_u16 (stream->src_port);
217       udp->dst_port = clib_host_to_net_u16 (frm->collector_port);
218       udp->checksum = 0;
219
220       h->export_time = clib_host_to_net_u32 ((u32)
221                                          (((f64) frm->unix_time_0) +
222                                           (vlib_time_now (frm->vlib_main) -
223                                            frm->vlib_time_0)));
224       h->sequence_number = clib_host_to_net_u32 (stream->sequence_number++);
225       h->domain_id = clib_host_to_net_u32 (stream->domain_id);
226
227       *offset = (u32) (((u8 *) (s + 1)) - (u8 *) tp);
228 }
229 ```
230
231 ### fixup and transmit a flow record
232
233 ```{.c}
234    
235    static inline void
236    my_send_ipfix_pkt (flow_report_main_t * frm,
237                          vlib_frame_t * f, vlib_buffer_t * b0, u16 template_id)
238    {
239      ip4_ipfix_template_packet_t *tp;
240      ipfix_message_header_t *h = 0;
241      ipfix_set_header_t *s = 0;
242      ip4_header_t *ip;
243      udp_header_t *udp;
244      vlib_main_t *vm = frm->vlib_main;
245
246      tp = vlib_buffer_get_current (b0);
247      ip = (ip4_header_t *) & tp->ip4;
248      udp = (udp_header_t *) (ip + 1);
249      h = (ipfix_message_header_t *) (udp + 1);
250      s = (ipfix_set_header_t *) (h + 1);
251
252      s->set_id_length = ipfix_set_id_length (template_id,
253                                           b0->current_length -
254                                           (sizeof (*ip) + sizeof (*udp) +
255                                            sizeof (*h)));
256      h->version_length = version_length (b0->current_length -
257                                       (sizeof (*ip) + sizeof (*udp)));
258
259      ip->length = clib_host_to_net_u16 (b0->current_length);
260      ip->checksum = ip4_header_checksum (ip);
261      udp->length = clib_host_to_net_u16 (b0->current_length - sizeof (*ip));
262
263      if (frm->udp_checksum)
264        {
265          udp->checksum = ip4_tcp_udp_compute_checksum (vm, b0, ip);
266          if (udp->checksum == 0)
267         udp->checksum = 0xffff;
268        }
269
270      ASSERT (ip->checksum == ip4_header_checksum (ip));
271
272      vlib_put_frame_to_node (vm, ip4_lookup_node.index, f);
273    }  
274 ```
275
276 ### my_buffer_flow_record
277
278 This is the key routine which paints individual flow records into
279 an ipfix packet under construction. It's pretty straightforward
280 (albeit stateful) vpp data-plane code.
281
282
283 ```{.c}
284    static void
285    my_buffer_flow_record (u32 datum0, u32 datum1, ..., int do_flush)
286    {
287      my_logging_main_t *mlm = &my_logging_main;
288      flow_report_main_t *frm = &flow_report_main;
289      vlib_frame_t *f;
290      vlib_buffer_t *b0 = 0;
291      u32 bi0 = ~0;
292      u32 offset;
293      vlib_main_t *vm = frm->vlib_main;
294      u64 now;
295      vlib_buffer_free_list_t *fl;
296      my_flow_record_t my_flow_record;
297
298      if (!mlm->enabled)
299        return;
300
301      now = (u64) ((vlib_time_now (vm) - silm->vlib_time_0) * 1e3);
302      now += mlm->milisecond_time_0;
303
304      /* 
305       * (maybe) set up a packed structure from datum0...datumN 
306       * Otherwise, paint directly into the buffer below...
307       */
308      my_flow_record.xxx = datum0;
309      my_flow_record.yyy = datum1;
310
311
312      b0 = mlm->my_data_buffer;
313
314      if (PREDICT_FALSE (b0 == 0))
315        {
316          if (do_flush)
317         return;
318
319          if (vlib_buffer_alloc (vm, &bi0, 1) != 1)
320         {
321           clib_warning ("can't allocate ipfix data buffer");
322           return;
323         }
324
325          b0 = mlm->my_data_buffer = vlib_get_buffer (vm, bi0);
326          fl =
327         vlib_buffer_get_free_list (vm, VLIB_BUFFER_DEFAULT_FREE_LIST_INDEX);
328          vlib_buffer_init_for_free_list (b0, fl);
329          VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b0);
330          offset = 0;
331        }
332      else
333        {
334          bi0 = vlib_get_buffer_index (vm, b0);
335          offset = mlm->my_next_record_offset;
336        }
337
338      f = mlm->my_ipfix_frame;
339      if (PREDICT_FALSE (f == 0))
340        {
341          u32 *to_next;
342          f = vlib_get_frame_to_node (vm, ip4_lookup_node.index);
343          mlm->my_ipfix_frame = f;
344          to_next = vlib_frame_vector_args (f);
345          to_next[0] = bi0;
346          f->n_vectors = 1;
347        }
348
349      if (PREDICT_FALSE (offset == 0))
350        my_flow_report_header (frm, b0, &offset);
351
352      if (PREDICT_TRUE (do_flush == 0))
353        {
354          /* paint time stamp into buffer */
355          clib_memcpy (b0->data + offset, &time_stamp, sizeof (time_stamp));
356          offset += sizeof (time_stamp);
357
358          /* Paint the new ipfix data record into the buffer */
359          clib_memcpy (b0->data + offset, &my_flow_record, 
360                      sizeof (my_flow_record));
361          offset += sizeof (my_flow_record);
362          b0->current_length += sizeof(my_flow_record);
363        }
364
365      if (PREDICT_FALSE
366          (do_flush || (offset + sizeof (my_flow_record)) > frm->path_mtu))
367        {
368          my_send_ipfix_pkt (frm, f, b0, mlm->template_id);
369          mlm->my_ipfix_frame = 0;
370          mlm->my_data_buffer = 0;
371          offset = 0;
372        }
373      mlm->next_record_offset = offset;
374    }
375 ```