Nathan Skrzypczak | d4a7064 | 2021-10-08 14:01:27 +0200 | [diff] [blame] | 1 | .. _ipfix_doc: |
| 2 | |
| 3 | IPFIX support |
| 4 | ============= |
| 5 | |
| 6 | VPP includes a high-performance IPFIX record exporter. This note |
| 7 | explains how to use the internal APIs to export IPFIX data, and how to |
| 8 | configure and send the required IPFIX templates. |
| 9 | |
| 10 | As you’ll see, a bit of typing is required. |
| 11 | |
| 12 | First: create an ipfix “report” |
| 13 | ------------------------------- |
| 14 | |
| 15 | Include the flow report header file, fill out a @ref |
| 16 | vnet_flow_report_add_del_args_t structure, and call |
| 17 | vnet_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 | |
| 140 | Several things are worth describing in more detail. |
| 141 | |
| 142 | vnet_flow_rewrite_generic_callback programming |
| 143 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 144 | |
| 145 | This generic callback helps build ipfix template packets. When |
| 146 | registering an ipfix report, pass an (array, count) of ipfix elements as |
| 147 | shown above. |
| 148 | |
| 149 | my_flow_data_callback |
| 150 | ~~~~~~~~~~~~~~~~~~~~~ |
| 151 | |
| 152 | The ipfix flow export infrastructure calls this callback to flush the |
| 153 | current ipfix packet; to make sure that ipfix data is not retained for |
| 154 | an unreasonably long period of time. |
| 155 | |
| 156 | We typically code it as shown below, to call an application-specific |
| 157 | function 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 | |
| 173 | my_flow_data_header |
| 174 | ~~~~~~~~~~~~~~~~~~~ |
| 175 | |
| 176 | This 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 | |
| 276 | This is the key routine which paints individual flow records into an |
| 277 | ipfix packet under construction. It’s pretty straightforward (albeit |
| 278 | stateful) vpp data-plane code. The code shown below is thread-safe by |
| 279 | construction. |
| 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 | } |