35abf979bd9ce27e21e450cfee436def7f4566fe
[vpp.git] / src / vnet / ipfix-export / flow_report_classify.c
1 /*
2  * Copyright (c) 2015 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #include <vnet/ipfix-export/flow_report.h>
16 #include <vnet/ipfix-export/flow_report_classify.h>
17 #include <vnet/api_errno.h>
18 #include <vnet/classify/vnet_classify.h>
19 #include <vnet/ip/ip4.h>
20 #include <vnet/udp/udp_local.h>
21
22 /* Common prefix of tcp and udp headers
23  * containing only source and destination port fields */
24 typedef struct
25 {
26   u16 src_port, dst_port;
27 } tcpudp_header_t;
28
29 flow_report_classify_main_t flow_report_classify_main;
30
31 u8 *
32 ipfix_classify_template_rewrite (flow_report_main_t * frm,
33                                  flow_report_t * fr,
34                                  ip4_address_t * collector_address,
35                                  ip4_address_t * src_address,
36                                  u16 collector_port,
37                                  ipfix_report_element_t * elts,
38                                  u32 n_elts, u32 * stream_index)
39 {
40   flow_report_classify_main_t *fcm = &flow_report_classify_main;
41   vnet_classify_table_t *tblp;
42   vnet_classify_main_t *vcm = &vnet_classify_main;
43   u32 flow_table_index = fr->opaque.as_uword;
44   u8 *ip_start;
45   ip4_header_t *ip;
46   ip6_header_t *ip6;
47   tcpudp_header_t *tcpudp;
48   udp_header_t *udp;
49   ipfix_message_header_t *h;
50   ipfix_set_header_t *s;
51   ipfix_template_header_t *t;
52   ipfix_field_specifier_t *f;
53   ipfix_field_specifier_t *first_field;
54   u8 *rewrite = 0;
55   ip4_ipfix_template_packet_t *tp;
56   u32 field_count = 0;
57   u32 field_index = 0;
58   flow_report_stream_t *stream;
59   u8 ip_version;
60   u8 transport_protocol;
61   u8 *virt_mask;
62   u8 *real_mask;
63   ipfix_exporter_t *exp = pool_elt_at_index (frm->exporters, 0);
64
65   stream = &exp->streams[fr->stream_index];
66
67   ipfix_classify_table_t *table = &fcm->tables[flow_table_index];
68
69   ip_version = table->ip_version;
70   transport_protocol = table->transport_protocol;
71
72   tblp = pool_elt_at_index (vcm->tables, table->classify_table_index);
73
74   virt_mask = (u8 *) (tblp->mask - tblp->skip_n_vectors);
75   real_mask = (u8 *) (tblp->mask);
76
77   /* Determine field count */
78   ip_start = virt_mask + sizeof (ethernet_header_t);
79 #define _(field,mask,item,length)                                             \
80   if (((u8 *)&field >= real_mask) && (memcmp(&field, &mask, length) == 0))    \
81     {                                                                         \
82       field_count++;                                                          \
83                                                                               \
84       fr->fields_to_send = clib_bitmap_set (fr->fields_to_send,               \
85                                             field_index, 1);                  \
86     }                                                                         \
87   field_index++;
88   foreach_ipfix_field;
89 #undef _
90
91   /* Add packetTotalCount manually */
92   field_count += 1;
93
94   /* $$$ enterprise fields, at some later date */
95
96   /* allocate rewrite space */
97   vec_validate_aligned (rewrite,
98                         sizeof (ip4_ipfix_template_packet_t)
99                         + field_count * sizeof (ipfix_field_specifier_t) - 1,
100                         CLIB_CACHE_LINE_BYTES);
101
102   tp = (ip4_ipfix_template_packet_t *) rewrite;
103   ip = (ip4_header_t *) & tp->ip4;
104   udp = (udp_header_t *) (ip + 1);
105   h = (ipfix_message_header_t *) (udp + 1);
106   s = (ipfix_set_header_t *) (h + 1);
107   t = (ipfix_template_header_t *) (s + 1);
108   first_field = f = (ipfix_field_specifier_t *) (t + 1);
109
110   ip->ip_version_and_header_length = 0x45;
111   ip->ttl = 254;
112   ip->protocol = IP_PROTOCOL_UDP;
113   ip->src_address.as_u32 = src_address->as_u32;
114   ip->dst_address.as_u32 = collector_address->as_u32;
115   udp->src_port = clib_host_to_net_u16 (stream->src_port);
116   udp->dst_port = clib_host_to_net_u16 (collector_port);
117   udp->length = clib_host_to_net_u16 (vec_len (rewrite) - sizeof (*ip));
118
119   /* FIXUP: message header export_time */
120   /* FIXUP: message header sequence_number */
121   h->domain_id = clib_host_to_net_u32 (stream->domain_id);
122
123   /* Take another trip through the mask and build the template */
124   ip_start = virt_mask + sizeof (ethernet_header_t);
125 #define _(field,mask,item,length)                                             \
126   if (((u8 *)&field >= real_mask) && (memcmp(&field, &mask, length) == 0))    \
127     {                                                                         \
128       f->e_id_length = ipfix_e_id_length (0 /* enterprise */,                 \
129                                           item, length);                      \
130       f++;                                                                    \
131     }
132   foreach_ipfix_field;
133 #undef _
134
135   /* Add packetTotalCount manually */
136   f->e_id_length =
137     ipfix_e_id_length (0 /* enterprise */ , packetTotalCount, 8);
138   f++;
139
140   /* Back to the template packet... */
141   ip = (ip4_header_t *) & tp->ip4;
142   udp = (udp_header_t *) (ip + 1);
143
144   ASSERT (f - first_field);
145   /* Field count in this template */
146   t->id_count = ipfix_id_count (fr->template_id, f - first_field);
147
148   /* set length in octets */
149   s->set_id_length =
150     ipfix_set_id_length (2 /* set_id */ , (u8 *) f - (u8 *) s);
151
152   /* message length in octets */
153   h->version_length = version_length ((u8 *) f - (u8 *) h);
154
155   ip->length = clib_host_to_net_u16 ((u8 *) f - (u8 *) ip);
156   ip->checksum = ip4_header_checksum (ip);
157
158   return rewrite;
159 }
160
161 vlib_frame_t *
162 ipfix_classify_send_flows (flow_report_main_t * frm,
163                            flow_report_t * fr,
164                            vlib_frame_t * f, u32 * to_next, u32 node_index)
165 {
166   flow_report_classify_main_t *fcm = &flow_report_classify_main;
167   vnet_classify_main_t *vcm = &vnet_classify_main;
168   u32 flow_table_index = fr->opaque.as_uword;
169   vnet_classify_table_t *t;
170   vnet_classify_bucket_t *b;
171   vnet_classify_entry_t *v, *save_v;
172   vlib_buffer_t *b0 = 0;
173   u32 next_offset = 0;
174   u32 record_offset = 0;
175   u32 bi0 = ~0;
176   int i, j, k;
177   ip4_ipfix_template_packet_t *tp;
178   ipfix_message_header_t *h = 0;
179   ipfix_set_header_t *s = 0;
180   u8 *ip_start;
181   ip4_header_t *ip;
182   ip6_header_t *ip6;
183   tcpudp_header_t *tcpudp;
184   udp_header_t *udp;
185   int field_index;
186   u32 records_this_buffer;
187   u16 new_l0, old_l0;
188   ip_csum_t sum0;
189   vlib_main_t *vm = frm->vlib_main;
190   flow_report_stream_t *stream;
191   u8 ip_version;
192   u8 transport_protocol;
193   u8 *virt_key;
194   ipfix_exporter_t *exp = pool_elt_at_index (frm->exporters, 0);
195
196   stream = &exp->streams[fr->stream_index];
197
198   ipfix_classify_table_t *table = &fcm->tables[flow_table_index];
199
200   ip_version = table->ip_version;
201   transport_protocol = table->transport_protocol;
202
203   t = pool_elt_at_index (vcm->tables, table->classify_table_index);
204
205   clib_spinlock_lock (&t->writer_lock);
206
207   for (i = 0; i < t->nbuckets; i++)
208     {
209       b = &t->buckets[i];
210       if (b->offset == 0)
211         continue;
212
213       save_v = vnet_classify_get_entry (t, b->offset);
214       for (j = 0; j < (1 << b->log2_pages); j++)
215         {
216           for (k = 0; k < t->entries_per_page; k++)
217             {
218               v = vnet_classify_entry_at_index
219                 (t, save_v, j * t->entries_per_page + k);
220
221               if (vnet_classify_entry_is_free (v))
222                 continue;
223
224               /* OK, we have something to send... */
225               if (PREDICT_FALSE (b0 == 0))
226                 {
227                   if (vlib_buffer_alloc (vm, &bi0, 1) != 1)
228                     goto flush;
229                   b0 = vlib_get_buffer (vm, bi0);
230
231                   u32 copy_len = sizeof (ip4_header_t) +
232                     sizeof (udp_header_t) + sizeof (ipfix_message_header_t);
233                   clib_memcpy_fast (b0->data, fr->rewrite, copy_len);
234                   b0->current_data = 0;
235                   b0->current_length = copy_len;
236                   b0->flags |= VLIB_BUFFER_TOTAL_LENGTH_VALID;
237                   vnet_buffer (b0)->sw_if_index[VLIB_RX] = 0;
238                   vnet_buffer (b0)->sw_if_index[VLIB_TX] = exp->fib_index;
239
240                   tp = vlib_buffer_get_current (b0);
241                   ip = (ip4_header_t *) & tp->ip4;
242                   udp = (udp_header_t *) (ip + 1);
243                   h = (ipfix_message_header_t *) (udp + 1);
244                   s = (ipfix_set_header_t *) (h + 1);
245
246                   /* FIXUP: message header export_time */
247                   h->export_time = (u32)
248                     (((f64) frm->unix_time_0) +
249                      (vlib_time_now (frm->vlib_main) - frm->vlib_time_0));
250                   h->export_time = clib_host_to_net_u32 (h->export_time);
251
252                   /* FIXUP: message header sequence_number */
253                   h->sequence_number = stream->sequence_number;
254                   h->sequence_number =
255                     clib_host_to_net_u32 (h->sequence_number);
256
257                   next_offset = (u32) (((u8 *) (s + 1)) - (u8 *) tp);
258                   record_offset = next_offset;
259                   records_this_buffer = 0;
260                 }
261
262               field_index = 0;
263               virt_key = (u8 *) (v->key - t->skip_n_vectors);
264               ip_start = virt_key + sizeof (ethernet_header_t);
265 #define _(field,mask,item,length)                                       \
266               if (clib_bitmap_get (fr->fields_to_send, field_index))    \
267                 {                                                       \
268                   clib_memcpy_fast (b0->data + next_offset, &field,          \
269                           length);                                      \
270                   next_offset += length;                                \
271                 }                                                       \
272               field_index++;
273               foreach_ipfix_field;
274 #undef _
275
276               /* Add packetTotalCount manually */
277               {
278                 u64 packets = clib_host_to_net_u64 (v->hits);
279                 clib_memcpy_fast (b0->data + next_offset, &packets,
280                                   sizeof (packets));
281                 next_offset += sizeof (packets);
282               }
283               records_this_buffer++;
284               stream->sequence_number++;
285
286               /* Next record will have the same size as this record */
287               u32 next_record_size = next_offset - record_offset;
288               record_offset = next_offset;
289
290               if (next_offset + next_record_size > exp->path_mtu)
291                 {
292                   s->set_id_length = ipfix_set_id_length (fr->template_id,
293                                                           next_offset -
294                                                           (sizeof (*ip) +
295                                                            sizeof (*udp) +
296                                                            sizeof (*h)));
297                   h->version_length =
298                     version_length (next_offset -
299                                     (sizeof (*ip) + sizeof (*udp)));
300                   b0->current_length = next_offset;
301                   b0->flags |= VLIB_BUFFER_TOTAL_LENGTH_VALID;
302
303                   tp = vlib_buffer_get_current (b0);
304                   ip = (ip4_header_t *) & tp->ip4;
305                   udp = (udp_header_t *) (ip + 1);
306
307                   sum0 = ip->checksum;
308                   old_l0 = ip->length;
309                   new_l0 = clib_host_to_net_u16 ((u16) next_offset);
310
311                   sum0 = ip_csum_update (sum0, old_l0, new_l0, ip4_header_t,
312                                          length /* changed member */ );
313
314                   ip->checksum = ip_csum_fold (sum0);
315                   ip->length = new_l0;
316                   udp->length =
317                     clib_host_to_net_u16 (b0->current_length - sizeof (*ip));
318
319                   if (exp->udp_checksum)
320                     {
321                       /* RFC 7011 section 10.3.2. */
322                       udp->checksum =
323                         ip4_tcp_udp_compute_checksum (vm, b0, ip);
324                       if (udp->checksum == 0)
325                         udp->checksum = 0xffff;
326                     }
327
328                   ASSERT (ip4_header_checksum_is_valid (ip));
329
330                   to_next[0] = bi0;
331                   f->n_vectors++;
332                   to_next++;
333
334                   if (f->n_vectors == VLIB_FRAME_SIZE)
335                     {
336                       vlib_put_frame_to_node (vm, node_index, f);
337                       f = vlib_get_frame_to_node (vm, node_index);
338                       f->n_vectors = 0;
339                       to_next = vlib_frame_vector_args (f);
340                     }
341                   b0 = 0;
342                   bi0 = ~0;
343                 }
344             }
345         }
346     }
347
348 flush:
349   if (b0)
350     {
351       s->set_id_length = ipfix_set_id_length (fr->template_id,
352                                               next_offset -
353                                               (sizeof (*ip) + sizeof (*udp) +
354                                                sizeof (*h)));
355       h->version_length = version_length (next_offset -
356                                           (sizeof (*ip) + sizeof (*udp)));
357       b0->current_length = next_offset;
358       b0->flags |= VLIB_BUFFER_TOTAL_LENGTH_VALID;
359
360       tp = vlib_buffer_get_current (b0);
361       ip = (ip4_header_t *) & tp->ip4;
362       udp = (udp_header_t *) (ip + 1);
363
364       sum0 = ip->checksum;
365       old_l0 = ip->length;
366       new_l0 = clib_host_to_net_u16 ((u16) next_offset);
367
368       sum0 = ip_csum_update (sum0, old_l0, new_l0, ip4_header_t,
369                              length /* changed member */ );
370
371       ip->checksum = ip_csum_fold (sum0);
372       ip->length = new_l0;
373       udp->length = clib_host_to_net_u16 (b0->current_length - sizeof (*ip));
374
375       if (exp->udp_checksum)
376         {
377           /* RFC 7011 section 10.3.2. */
378           udp->checksum = ip4_tcp_udp_compute_checksum (vm, b0, ip);
379           if (udp->checksum == 0)
380             udp->checksum = 0xffff;
381         }
382
383       ASSERT (ip4_header_checksum_is_valid (ip));
384
385       to_next[0] = bi0;
386       f->n_vectors++;
387
388       b0 = 0;
389       bi0 = ~0;
390     }
391
392   clib_spinlock_unlock (&t->writer_lock);
393   return f;
394 }
395
396 static clib_error_t *
397 ipfix_classify_table_add_del_command_fn (vlib_main_t * vm,
398                                          unformat_input_t * input,
399                                          vlib_cli_command_t * cmd)
400 {
401   flow_report_classify_main_t *fcm = &flow_report_classify_main;
402   flow_report_main_t *frm = &flow_report_main;
403   vnet_flow_report_add_del_args_t args;
404   ipfix_classify_table_t *table;
405   int rv;
406   int is_add = -1;
407   u32 classify_table_index = ~0;
408   u8 ip_version = 0;
409   u8 transport_protocol = 255;
410   clib_error_t *error = 0;
411
412   if (fcm->src_port == 0)
413     clib_error_return (0, "call 'set ipfix classify stream' first");
414
415   clib_memset (&args, 0, sizeof (args));
416
417   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
418     {
419       if (unformat (input, "add"))
420         is_add = 1;
421       else if (unformat (input, "del"))
422         is_add = 0;
423       else if (unformat (input, "%d", &classify_table_index))
424         ;
425       else if (unformat (input, "ip4"))
426         ip_version = 4;
427       else if (unformat (input, "ip6"))
428         ip_version = 6;
429       else if (unformat (input, "tcp"))
430         transport_protocol = 6;
431       else if (unformat (input, "udp"))
432         transport_protocol = 17;
433       else
434         return clib_error_return (0, "unknown input `%U'",
435                                   format_unformat_error, input);
436     }
437
438   if (is_add == -1)
439     return clib_error_return (0, "expecting: add|del");
440   if (classify_table_index == ~0)
441     return clib_error_return (0, "classifier table not specified");
442   if (ip_version == 0)
443     return clib_error_return (0, "IP version not specified");
444
445   table = 0;
446   int i;
447   for (i = 0; i < vec_len (fcm->tables); i++)
448     if (ipfix_classify_table_index_valid (i))
449       if (fcm->tables[i].classify_table_index == classify_table_index)
450         {
451           table = &fcm->tables[i];
452           break;
453         }
454
455   if (is_add)
456     {
457       if (table)
458         return clib_error_return (0,
459                                   "Specified classifier table already used");
460       table = ipfix_classify_add_table ();
461       table->classify_table_index = classify_table_index;
462     }
463   else
464     {
465       if (!table)
466         return clib_error_return (0,
467                                   "Specified classifier table not registered");
468     }
469
470   table->ip_version = ip_version;
471   table->transport_protocol = transport_protocol;
472
473   args.opaque.as_uword = table - fcm->tables;
474   args.rewrite_callback = ipfix_classify_template_rewrite;
475   args.flow_data_callback = ipfix_classify_send_flows;
476   args.is_add = is_add;
477   args.domain_id = fcm->domain_id;
478   args.src_port = fcm->src_port;
479
480   rv = vnet_flow_report_add_del (frm, &args, NULL);
481
482   error = flow_report_add_del_error_to_clib_error (rv);
483
484   /* If deleting, or add failed */
485   if (is_add == 0 || (rv && is_add))
486     ipfix_classify_delete_table (table - fcm->tables);
487
488   return error;
489 }
490
491 /* *INDENT-OFF* */
492 VLIB_CLI_COMMAND (ipfix_classify_table_add_del_command, static) = {
493   .path = "ipfix classify table",
494   .short_help = "ipfix classify table add|del <table-index>",
495   .function = ipfix_classify_table_add_del_command_fn,
496 };
497 /* *INDENT-ON* */
498
499 static clib_error_t *
500 set_ipfix_classify_stream_command_fn (vlib_main_t * vm,
501                                       unformat_input_t * input,
502                                       vlib_cli_command_t * cmd)
503 {
504   flow_report_classify_main_t *fcm = &flow_report_classify_main;
505   flow_report_main_t *frm = &flow_report_main;
506   u32 domain_id = 1;
507   u32 src_port = UDP_DST_PORT_ipfix;
508
509   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
510     {
511       if (unformat (input, "domain %d", &domain_id))
512         ;
513       else if (unformat (input, "src-port %d", &src_port))
514         ;
515       else
516         return clib_error_return (0, "unknown input `%U'",
517                                   format_unformat_error, input);
518     }
519
520   if (fcm->src_port != 0 &&
521       (fcm->domain_id != domain_id || fcm->src_port != (u16) src_port))
522     {
523       int rv = vnet_stream_change (frm, fcm->domain_id, fcm->src_port,
524                                    domain_id, (u16) src_port);
525       ASSERT (rv == 0);
526     }
527
528   fcm->domain_id = domain_id;
529   fcm->src_port = (u16) src_port;
530
531   return 0;
532 }
533
534 /* *INDENT-OFF* */
535 VLIB_CLI_COMMAND (set_ipfix_classify_stream_command, static) = {
536   .path = "set ipfix classify stream",
537   .short_help = "set ipfix classify stream"
538                 "[domain <domain-id>] [src-port <src-port>]",
539   .function = set_ipfix_classify_stream_command_fn,
540 };
541 /* *INDENT-ON* */
542
543 static clib_error_t *
544 flow_report_classify_init (vlib_main_t * vm)
545 {
546   clib_error_t *error;
547
548   if ((error = vlib_call_init_function (vm, flow_report_init)))
549     return error;
550
551   return 0;
552 }
553
554 VLIB_INIT_FUNCTION (flow_report_classify_init);
555
556 /*
557  * fd.io coding-style-patch-verification: ON
558  *
559  * Local Variables:
560  * eval: (c-set-style "gnu")
561  * End:
562  */