blob: ac660b4bc9334dbcea5f4756376214eecab618f0 [file] [log] [blame]
Nathan Skrzypczakd4a70642021-10-08 14:01:27 +02001.. _ipfix_doc:
2
3IPFIX support
4=============
5
6VPP includes a high-performance IPFIX record exporter. This note
7explains how to use the internal APIs to export IPFIX data, and how to
8configure and send the required IPFIX templates.
9
10As youll see, a bit of typing is required.
11
12First: create an ipfix report
13-------------------------------
14
15Include the flow report header file, fill out a @ref
16vnet_flow_report_add_del_args_t structure, and call
17vnet_flow_report_add_del.
18
19.. code:: c
20
21 #include <vnet/ipfix-export/flow_report.h>
22 /* Defined in flow_report.h, of interest when constructing reports */
23
24 /* ipfix field definitions for a particular report */
25 typedef struct
26 {
27 u32 info_element;
28 u32 size;
29 } ipfix_report_element_t;
30
31 /* Report add/del argument structure */
32 typedef struct
33 {
34 /* Callback to flush current ipfix packet / frame */
35 vnet_flow_data_callback_t *flow_data_callback;
36
37 /* Callback to build the template packet rewrite string */
38 vnet_flow_rewrite_callback_t *rewrite_callback;
39
40 /* List of ipfix elements in the report */
41 ipfix_report_element_t *report_elements;
42 u32 n_report_elements;
43 /* Kept in flow report, used e.g. by flow classifier */
44 opaque_t opaque;
45 /* Add / delete a report */
46 int is_add;
47 /* Ipfix "domain-ID", see RFC, set as desired */
48 u32 domain_id;
49 /* ipfix packet source port, often set to UDP_DST_PORT_ipfix */
50 u16 src_port;
51 /* Set by ipfix infra, needed to send data packets */
52 u32 *stream_indexp;
53 } vnet_flow_report_add_del_args_t;
54
55 /* Private header file contents */
56
57 /* Report ipfix element definition */
58 #define foreach_simple_report_ipfix_element \
59 _(sourceIPv4Address, 4) \
60 _(destinationIPv4Address, 4) \
61 _(sourceTransportPort, 2) \
62 _(destinationTransportPort, 2) \
63 _(protocolIdentifier, 1) \
64 _(flowStartMicroseconds, 8) \
65 _(flowEndMicroseconds, 8)
66
67 static ipfix_report_element_t simple_report_elements[] = {
68 #define _(a,b) {a,b},
69 foreach_simple_report_ipfix_element
70 #undef _
71 };
72
73 typedef struct
74 {
75 /** Buffers and frames, per thread */
76 vlib_buffer_t **buffers_by_thread;
77 vlib_frame_t **frames_by_thread;
78 u32 *next_record_offset_by_thread;
79
80 /** Template ID's */
81 u16 *template_ids;
82
83 /** Time reference pair */
84 u64 usec_time_0;
85 f64 vlib_time_0;
86
87 /** Stream index */
88 u32 stream_index;
89
90 /* Convenience */
91 flow_report_main_t *flow_report_main;
92 vlib_main_t *vlib_main;
93 vnet_main_t *vnet_main;
94 } my_logging_main_t;
95
96 extern my_logging_main_t my_logging_main;
97
98 ...
99
100 /* Recitations */
101 flow_report_main_t *frm = &flow_report_main;
102 my_logging_main_t *mlm = &my_logging_main;
103 vnet_flow_report_add_del_args_t a;
104 int rv;
105 u16 template_id;
106
107 ...
108
109 /* Init function: set up time reference pair */
110 mlm->vlib_time_0 = vlib_time_now (vm);
111 mlm->milisecond_time_0 = unix_time_now_nsec () * 1e-6;
112
113 ...
114
115 /* Create a report */
116 memset (&a, 0, sizeof (a));
117 a.is_add = 1 /* to enable the report */;
118 a.domain_id = 1 /* pick a domain ID */;
119 a.src_port = UDP_DST_PORT_ipfix /* src port for reports */;
120
121 /* Use the generic template packet rewrite string generator */
122 a.rewrite_callback = vnet_flow_rewrite_generic_callback;
123
124 /* Supply a list of ipfix report elements */
125 a.report_elements = simple_report_elements;
126 a.n_report_elements = ARRAY_LEN (simple_report_elements);
127
128 /* Pointer to the ipfix stream index, set by the report infra */
129 a.stream_indexp = &mlm->stream_index;
130 a.flow_data_callback = my_flow_data_callback;
131
132 /* Create the report */
133 rv = vnet_flow_report_add_del (frm, &a, &template_id);
134 if (rv)
135 oops...
136
137 /* Save the template-ID for later use */
138 mlm->template_id = template_id;
139
140Several things are worth describing in more detail.
141
142vnet_flow_rewrite_generic_callback programming
143~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
144
145This generic callback helps build ipfix template packets. When
146registering an ipfix report, pass an (array, count) of ipfix elements as
147shown above.
148
149my_flow_data_callback
150~~~~~~~~~~~~~~~~~~~~~
151
152The ipfix flow export infrastructure calls this callback to flush the
153current ipfix packet; to make sure that ipfix data is not retained for
154an unreasonably long period of time.
155
156We typically code it as shown below, to call an application-specific
157function with (uninteresting arguments), and do_flush = 1”:
158
159.. code:: c
160
161
162 vlib_frame_t *my_flow_data_callback
163 (flow_report_main_t * frm,
164 flow_report_t * fr,
165 vlib_frame_t * f,
166 u32 * to_next, u32 node_index)
167 {
168
169 my_buffer_flow_record (0, ... , 0, 1 /* do_flush */);
170 return f;
171 }
172
173my_flow_data_header
174~~~~~~~~~~~~~~~~~~~
175
176This function creates the packet header for an ipfix data packet
177
178.. code:: c
179
180
181 static inline void
182 my_flow_report_header (flow_report_main_t * frm,
183 vlib_buffer_t * b0, u32 * offset)
184 {
185 my_logging_main_t *mlm = &my_logging_main;
186 flow_report_stream_t *stream;
187 ip4_ipfix_template_packet_t *tp;
188 ipfix_message_header_t *h = 0;
189
190
191 ipfix_set_header_t *s = 0;
192 ip4_header_t *ip;
193 udp_header_t *udp;
194
195 stream = &frm->streams[mlm->stream_index];
196
197 b0->current_data = 0;
198 b0->current_length = sizeof (*ip) + sizeof (*udp) + sizeof (*h) +
199 sizeof (*s);
200 b0->flags |= (VLIB_BUFFER_TOTAL_LENGTH_VALID | VNET_BUFFER_F_FLOW_REPORT);
201 vnet_buffer (b0)->sw_if_index[VLIB_RX] = 0;
202 vnet_buffer (b0)->sw_if_index[VLIB_TX] = frm->fib_index;
203 tp = vlib_buffer_get_current (b0);
204 ip = (ip4_header_t *) & tp->ip4;
205 udp = (udp_header_t *) (ip + 1);
206 h = (ipfix_message_header_t *) (udp + 1);
207 s = (ipfix_set_header_t *) (h + 1);
208
209 ip->ip_version_and_header_length = 0x45;
210 ip->ttl = 254;
211 ip->protocol = IP_PROTOCOL_UDP;
212 ip->flags_and_fragment_offset = 0;
213 ip->src_address.as_u32 = frm->src_address.as_u32;
214 ip->dst_address.as_u32 = frm->ipfix_collector.as_u32;
215 udp->src_port = clib_host_to_net_u16 (stream->src_port);
216 udp->dst_port = clib_host_to_net_u16 (frm->collector_port);
217 udp->checksum = 0;
218
219 h->export_time = clib_host_to_net_u32 ((u32)
220 (((f64) frm->unix_time_0) +
221 (vlib_time_now (frm->vlib_main) -
222 frm->vlib_time_0)));
223 h->sequence_number = clib_host_to_net_u32 (stream->sequence_number++);
224 h->domain_id = clib_host_to_net_u32 (stream->domain_id);
225
226 *offset = (u32) (((u8 *) (s + 1)) - (u8 *) tp);
227 }
228
229### fixup and transmit a flow record
230
231.. code:: c
232
233
234 static inline void
235 my_send_ipfix_pkt (flow_report_main_t * frm,
236 vlib_frame_t * f, vlib_buffer_t * b0, u16 template_id)
237 {
238 ip4_ipfix_template_packet_t *tp;
239 ipfix_message_header_t *h = 0;
240 ipfix_set_header_t *s = 0;
241 ip4_header_t *ip;
242 udp_header_t *udp;
243 vlib_main_t *vm = frm->vlib_main;
244
245 tp = vlib_buffer_get_current (b0);
246 ip = (ip4_header_t *) & tp->ip4;
247 udp = (udp_header_t *) (ip + 1);
248 h = (ipfix_message_header_t *) (udp + 1);
249 s = (ipfix_set_header_t *) (h + 1);
250
251 s->set_id_length = ipfix_set_id_length (template_id,
252 b0->current_length -
253 (sizeof (*ip) + sizeof (*udp) +
254 sizeof (*h)));
255 h->version_length = version_length (b0->current_length -
256 (sizeof (*ip) + sizeof (*udp)));
257
258 ip->length = clib_host_to_net_u16 (b0->current_length);
259 ip->checksum = ip4_header_checksum (ip);
260 udp->length = clib_host_to_net_u16 (b0->current_length - sizeof (*ip));
261
262 if (frm->udp_checksum)
263 {
264 udp->checksum = ip4_tcp_udp_compute_checksum (vm, b0, ip);
265 if (udp->checksum == 0)
266 udp->checksum = 0xffff;
267 }
268
269 ASSERT (ip4_header_checksum_is_valid (ip));
270
271 vlib_put_frame_to_node (vm, ip4_lookup_node.index, f);
272 }
273
274### my_buffer_flow_record
275
276This is the key routine which paints individual flow records into an
277ipfix packet under construction. Its pretty straightforward (albeit
278stateful) vpp data-plane code. The code shown below is thread-safe by
279construction.
280
281.. code:: c
282
283 static inline void
284 my_buffer_flow_record_internal (my_flow_record_t * rp, int do_flush,
285 u32 thread_index)
286 {
287 vlib_main_t *vm = vlib_mains[thread_index];
288 my_logging_main_t *mlm = &jvp_ipfix_main;
289 flow_report_main_t *frm = &flow_report_main;
290 vlib_frame_t *f;
291 vlib_buffer_t *b0 = 0;
292 u32 bi0 = ~0;
293 u32 offset;
294
295 b0 = mlm->buffers_by_thread[thread_index];
296
297 if (PREDICT_FALSE (b0 == 0))
298 {
299 if (do_flush)
300 return;
301
302 if (vlib_buffer_alloc (vm, &bi0, 1) != 1)
303 {
304 clib_warning ("can't allocate ipfix data buffer");
305 return;
306 }
307
308 b0 = vlib_get_buffer (vm, bi0);
309 offset = 0;
310 mlm->buffers_by_thread[thread_index] = b0;
311 }
312 else
313 {
314 bi0 = vlib_get_buffer_index (vm, b0);
315 offset = mlm->next_record_offset_by_thread[thread_index];
316 }
317
318 f = mlm->frames_by_thread[thread_index];
319 if (PREDICT_FALSE (f == 0))
320 {
321 u32 *to_next;
322 f = vlib_get_frame_to_node (vm, ip4_lookup_node.index);
323 mlm->frames_by_thread[thread_index] = f;
324 to_next = vlib_frame_vector_args (f);
325 to_next[0] = bi0;
326 f->n_vectors = 1;
327 mlm->frames_by_thread[thread_index] = f;
328 }
329
330 if (PREDICT_FALSE (offset == 0))
331 my_flow_report_header (frm, b0, &offset);
332
333 if (PREDICT_TRUE (do_flush == 0))
334 {
335 /* Paint the new ipfix data record into the buffer */
336 clib_memcpy (b0->data + offset, rp, sizeof (*rp));
337 offset += sizeof (*rp);
338 b0->current_length += sizeof (*rp);
339 }
340
341 if (PREDICT_FALSE (do_flush || (offset + sizeof (*rp)) > frm->path_mtu))
342 {
343 /* Nothing to send? */
344 if (offset == 0)
345 return;
346
347 send_ipfix_pkt (frm, f, b0, mlm->template_ids[0]);
348 mlm->buffers_by_thread[thread_index] = 0;
349 mlm->frames_by_thread[thread_index] = 0;
350 offset = 0;
351 }
352 mlm->next_record_offset_by_thread[thread_index] = offset;
353 }
354
355 static void
356 my_buffer_flow_record (my_flow_record_t * rp, int do_flush)
357 {
358 u32 thread_index = vlib_get_thread_index();
359 my_buffer_flow_record_internal (rp, do_flush, thread_index);
360 }