pg: API cleanup
[vpp.git] / src / vnet / pg / cli.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 /*
16  * pg_cli.c: packet generator cli
17  *
18  * Copyright (c) 2008 Eliot Dresselhaus
19  *
20  * Permission is hereby granted, free of charge, to any person obtaining
21  * a copy of this software and associated documentation files (the
22  * "Software"), to deal in the Software without restriction, including
23  * without limitation the rights to use, copy, modify, merge, publish,
24  * distribute, sublicense, and/or sell copies of the Software, and to
25  * permit persons to whom the Software is furnished to do so, subject to
26  * the following conditions:
27  *
28  * The above copyright notice and this permission notice shall be
29  * included in all copies or substantial portions of the Software.
30  *
31  *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
32  *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
33  *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
34  *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
35  *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
36  *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
37  *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
38  */
39
40 #include <sys/stat.h>
41
42 #include <vnet/vnet.h>
43 #include <vnet/pg/pg.h>
44
45 #include <strings.h>
46 #include <vppinfra/pcap.h>
47
48
49 /* Root of all packet generator cli commands. */
50 /* *INDENT-OFF* */
51 VLIB_CLI_COMMAND (vlib_cli_pg_command, static) = {
52   .path = "packet-generator",
53   .short_help = "Packet generator commands",
54 };
55 /* *INDENT-ON* */
56
57 void
58 pg_enable_disable (u32 stream_index, int is_enable)
59 {
60   pg_main_t *pg = &pg_main;
61   pg_stream_t *s;
62
63   if (stream_index == ~0)
64     {
65       /* No stream specified: enable/disable all streams. */
66       /* *INDENT-OFF* */
67         pool_foreach (s, pg->streams, ({
68             pg_stream_enable_disable (pg, s, is_enable);
69         }));
70         /* *INDENT-ON* */
71     }
72   else
73     {
74       /* enable/disable specified stream. */
75       s = pool_elt_at_index (pg->streams, stream_index);
76       pg_stream_enable_disable (pg, s, is_enable);
77     }
78 }
79
80 clib_error_t *
81 pg_capture (pg_capture_args_t * a)
82 {
83   pg_main_t *pg = &pg_main;
84   pg_interface_t *pi;
85
86   if (a->is_enabled == 1)
87     {
88       struct stat sb;
89       if (stat (a->pcap_file_name, &sb) != -1)
90         return clib_error_return (0, "pcap file '%s' already exists.",
91                                   a->pcap_file_name);
92     }
93
94   pi = pool_elt_at_index (pg->interfaces, a->dev_instance);
95   vec_free (pi->pcap_file_name);
96   if ((pi->pcap_main.flags & PCAP_MAIN_INIT_DONE))
97     pcap_close (&pi->pcap_main);
98   clib_memset (&pi->pcap_main, 0, sizeof (pi->pcap_main));
99   pi->pcap_main.file_descriptor = -1;
100
101   if (a->is_enabled == 0)
102     return 0;
103
104   pi->pcap_file_name = a->pcap_file_name;
105   pi->pcap_main.file_name = (char *) pi->pcap_file_name;
106   pi->pcap_main.n_packets_to_capture = a->count;
107   pi->pcap_main.packet_type = PCAP_PACKET_TYPE_ethernet;
108
109   return 0;
110 }
111
112 static clib_error_t *
113 enable_disable_stream (vlib_main_t * vm,
114                        unformat_input_t * input, vlib_cli_command_t * cmd)
115 {
116   unformat_input_t _line_input, *line_input = &_line_input;
117   pg_main_t *pg = &pg_main;
118   int is_enable = cmd->function_arg != 0;
119   u32 stream_index = ~0;
120
121   if (!unformat_user (input, unformat_line_input, line_input))
122     goto doit;
123
124   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
125     {
126       if (unformat (line_input, "%U", unformat_hash_vec_string,
127                     pg->stream_index_by_name, &stream_index))
128         ;
129       else
130         return clib_error_create ("unknown input `%U'",
131                                   format_unformat_error, line_input);
132     }
133   unformat_free (line_input);
134
135 doit:
136   pg_enable_disable (stream_index, is_enable);
137
138   return 0;
139 }
140
141 /* *INDENT-OFF* */
142 VLIB_CLI_COMMAND (enable_streams_cli, static) = {
143   .path = "packet-generator enable-stream",
144   .short_help = "Enable packet generator streams",
145   .function = enable_disable_stream,
146   .function_arg = 1,            /* is_enable */
147 };
148 /* *INDENT-ON* */
149
150 /* *INDENT-OFF* */
151 VLIB_CLI_COMMAND (disable_streams_cli, static) = {
152   .path = "packet-generator disable-stream",
153   .short_help = "Disable packet generator streams",
154   .function = enable_disable_stream,
155   .function_arg = 0,            /* is_enable */
156 };
157 /* *INDENT-ON* */
158
159 static u8 *
160 format_pg_edit_group (u8 * s, va_list * va)
161 {
162   pg_edit_group_t *g = va_arg (*va, pg_edit_group_t *);
163
164   s =
165     format (s, "hdr-size %d, offset %d, ", g->n_packet_bytes,
166             g->start_byte_offset);
167   if (g->edit_function)
168     {
169       u8 *function_name;
170       u8 *junk_after_name;
171       function_name = format (0, "%U%c", format_clib_elf_symbol_with_address,
172                               g->edit_function, 0);
173       junk_after_name = function_name;
174       while (*junk_after_name && *junk_after_name != ' ')
175         junk_after_name++;
176       *junk_after_name = 0;
177       s = format (s, "edit-function %s, ", function_name);
178       vec_free (function_name);
179     }
180
181   return s;
182 }
183
184 static u8 *
185 format_pg_stream (u8 * s, va_list * va)
186 {
187   pg_stream_t *t = va_arg (*va, pg_stream_t *);
188   int verbose = va_arg (*va, int);
189
190   if (!t)
191     return format (s, "%-16s%=12s%=16s%s",
192                    "Name", "Enabled", "Count", "Parameters");
193
194   s = format (s, "%-16v%=12s%=16Ld",
195               t->name,
196               pg_stream_is_enabled (t) ? "Yes" : "No",
197               t->n_packets_generated);
198
199   int indent = format_get_indent (s);
200
201   s = format (s, "limit %Ld, ", t->n_packets_limit);
202   s = format (s, "rate %.2e pps, ", t->rate_packets_per_second);
203   s = format (s, "size %d%c%d, ",
204               t->min_packet_bytes,
205               t->packet_size_edit_type == PG_EDIT_RANDOM ? '+' : '-',
206               t->max_packet_bytes);
207   s = format (s, "buffer-size %d, ", t->buffer_bytes);
208   s = format (s, "worker %d, ", t->worker_index);
209
210   if (verbose)
211     {
212       pg_edit_group_t *g;
213   /* *INDENT-OFF* */
214   vec_foreach (g, t->edit_groups)
215     {
216       s = format (s, "\n%U%U", format_white_space, indent, format_pg_edit_group, g);
217     }
218   /* *INDENT-ON* */
219     }
220
221   return s;
222 }
223
224 static clib_error_t *
225 show_streams (vlib_main_t * vm,
226               unformat_input_t * input, vlib_cli_command_t * cmd)
227 {
228   pg_main_t *pg = &pg_main;
229   pg_stream_t *s;
230   int verbose = 0;
231
232   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
233     {
234       if (unformat (input, "verbose"))
235         verbose = 1;
236       else
237         break;
238     }
239
240   if (pool_elts (pg->streams) == 0)
241     {
242       vlib_cli_output (vm, "no streams currently defined");
243       goto done;
244     }
245
246   vlib_cli_output (vm, "%U", format_pg_stream, 0, 0);
247   /* *INDENT-OFF* */
248   pool_foreach (s, pg->streams, ({
249       vlib_cli_output (vm, "%U", format_pg_stream, s, verbose);
250     }));
251   /* *INDENT-ON* */
252
253 done:
254   return 0;
255 }
256
257 /* *INDENT-OFF* */
258 VLIB_CLI_COMMAND (show_streams_cli, static) = {
259   .path = "show packet-generator ",
260   .short_help = "show packet-generator [verbose]",
261   .function = show_streams,
262 };
263 /* *INDENT-ON* */
264
265 static clib_error_t *
266 pg_pcap_read (pg_stream_t * s, char *file_name)
267 {
268 #ifndef CLIB_UNIX
269   return clib_error_return (0, "no pcap support");
270 #else
271   pcap_main_t pm;
272   clib_error_t *error;
273   clib_memset (&pm, 0, sizeof (pm));
274   pm.file_name = file_name;
275   error = pcap_read (&pm);
276   s->replay_packet_templates = pm.packets_read;
277   s->replay_packet_timestamps = pm.timestamps;
278   s->min_packet_bytes = pm.min_packet_bytes;
279   s->max_packet_bytes = pm.max_packet_bytes;
280   s->buffer_bytes = pm.max_packet_bytes;
281
282   if (s->n_packets_limit == 0)
283     s->n_packets_limit = vec_len (pm.packets_read);
284
285   return error;
286 #endif /* CLIB_UNIX */
287 }
288
289 static uword
290 unformat_pg_stream_parameter (unformat_input_t * input, va_list * args)
291 {
292   pg_stream_t *s = va_arg (*args, pg_stream_t *);
293   f64 x;
294
295   if (unformat (input, "limit %f", &x))
296     s->n_packets_limit = x;
297
298   else if (unformat (input, "rate %f", &x))
299     s->rate_packets_per_second = x;
300
301   else if (unformat (input, "size %d-%d", &s->min_packet_bytes,
302                      &s->max_packet_bytes))
303     s->packet_size_edit_type = PG_EDIT_INCREMENT;
304
305   else if (unformat (input, "size %d+%d", &s->min_packet_bytes,
306                      &s->max_packet_bytes))
307     s->packet_size_edit_type = PG_EDIT_RANDOM;
308
309   else if (unformat (input, "buffer-size %d", &s->buffer_bytes))
310     ;
311
312   else
313     return 0;
314
315   return 1;
316 }
317
318 static clib_error_t *
319 validate_stream (pg_stream_t * s)
320 {
321   if (s->max_packet_bytes < s->min_packet_bytes)
322     return clib_error_create ("max-size < min-size");
323
324   u32 hdr_size = pg_edit_group_n_bytes (s, 0);
325   if (s->min_packet_bytes < hdr_size)
326     return clib_error_create ("min-size < total header size %d", hdr_size);
327   if (s->buffer_bytes == 0)
328     return clib_error_create ("buffer-size must be positive");
329
330   if (s->rate_packets_per_second < 0)
331     return clib_error_create ("negative rate");
332
333   return 0;
334 }
335
336 static clib_error_t *
337 new_stream (vlib_main_t * vm,
338             unformat_input_t * input, vlib_cli_command_t * cmd)
339 {
340   clib_error_t *error = 0;
341   u8 *tmp = 0;
342   u32 maxframe, hw_if_index;
343   unformat_input_t sub_input = { 0 };
344   int sub_input_given = 0;
345   vnet_main_t *vnm = vnet_get_main ();
346   pg_main_t *pg = &pg_main;
347   pg_stream_t s = { 0 };
348   char *pcap_file_name;
349
350   s.sw_if_index[VLIB_RX] = s.sw_if_index[VLIB_TX] = ~0;
351   s.node_index = ~0;
352   s.max_packet_bytes = s.min_packet_bytes = 64;
353   s.buffer_bytes = vlib_buffer_get_default_data_size (vm);
354   s.if_id = 0;
355   s.n_max_frame = VLIB_FRAME_SIZE;
356   pcap_file_name = 0;
357
358   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
359     {
360       if (unformat (input, "name %v", &tmp))
361         {
362           if (s.name)
363             vec_free (s.name);
364           s.name = tmp;
365         }
366
367       else if (unformat (input, "node %U",
368                          unformat_vnet_hw_interface, vnm, &hw_if_index))
369         {
370           vnet_hw_interface_t *hi = vnet_get_hw_interface (vnm, hw_if_index);
371
372           s.node_index = hi->output_node_index;
373           s.sw_if_index[VLIB_TX] = hi->sw_if_index;
374         }
375
376       else if (unformat (input, "source pg%u", &s.if_id))
377         ;
378
379       else if (unformat (input, "node %U",
380                          unformat_vlib_node, vm, &s.node_index))
381         ;
382       else if (unformat (input, "maxframe %u", &maxframe))
383         s.n_max_frame = s.n_max_frame < maxframe ? s.n_max_frame : maxframe;
384       else if (unformat (input, "worker %u", &s.worker_index))
385         ;
386
387       else if (unformat (input, "interface %U",
388                          unformat_vnet_sw_interface, vnm,
389                          &s.sw_if_index[VLIB_RX]))
390         ;
391       else if (unformat (input, "tx-interface %U",
392                          unformat_vnet_sw_interface, vnm,
393                          &s.sw_if_index[VLIB_TX]))
394         ;
395
396       else if (unformat (input, "pcap %s", &pcap_file_name))
397         ;
398
399       else if (!sub_input_given
400                && unformat (input, "data %U", unformat_input, &sub_input))
401         sub_input_given++;
402
403       else if (unformat_user (input, unformat_pg_stream_parameter, &s))
404         ;
405
406       else
407         {
408           error = clib_error_create ("unknown input `%U'",
409                                      format_unformat_error, input);
410           goto done;
411         }
412     }
413
414   if (!sub_input_given && !pcap_file_name)
415     {
416       error = clib_error_create ("no packet data given");
417       goto done;
418     }
419
420   if (s.node_index == ~0)
421     {
422       if (pcap_file_name != 0)
423         {
424           vlib_node_t *n =
425             vlib_get_node_by_name (vm, (u8 *) "ethernet-input");
426           s.node_index = n->index;
427         }
428       else
429         {
430           error = clib_error_create ("output interface or node not given");
431           goto done;
432         }
433     }
434
435   {
436     pg_node_t *n;
437
438     if (s.node_index < vec_len (pg->nodes))
439       n = pg->nodes + s.node_index;
440     else
441       n = 0;
442
443     if (s.worker_index >= vlib_num_workers ())
444       s.worker_index = 0;
445
446     if (pcap_file_name != 0)
447       {
448         error = pg_pcap_read (&s, pcap_file_name);
449         if (error)
450           goto done;
451         vec_free (pcap_file_name);
452       }
453
454     else if (n && n->unformat_edit
455              && unformat_user (&sub_input, n->unformat_edit, &s))
456       ;
457
458     else if (!unformat_user (&sub_input, unformat_pg_payload, &s))
459       {
460         error = clib_error_create
461           ("failed to parse packet data from `%U'",
462            format_unformat_error, &sub_input);
463         goto done;
464       }
465   }
466
467   error = validate_stream (&s);
468   if (error)
469     return error;
470
471   pg_stream_add (pg, &s);
472   return 0;
473
474 done:
475   pg_stream_free (&s);
476   unformat_free (&sub_input);
477   return error;
478 }
479
480 /* *INDENT-OFF* */
481 VLIB_CLI_COMMAND (new_stream_cli, static) = {
482   .path = "packet-generator new",
483   .function = new_stream,
484   .short_help = "Create packet generator stream",
485   .long_help =
486   "Create packet generator stream\n"
487   "\n"
488   "Arguments:\n"
489   "\n"
490   "name STRING          sets stream name\n"
491   "interface STRING     interface for stream output \n"
492   "node NODE-NAME       node for stream output\n"
493   "data STRING          specifies packet data\n"
494   "pcap FILENAME        read packet data from pcap file\n"
495   "rate PPS             rate to transfer packet data\n"
496   "maxframe NPKTS       maximum number of packets per frame\n",
497 };
498 /* *INDENT-ON* */
499
500 static clib_error_t *
501 del_stream (vlib_main_t * vm,
502             unformat_input_t * input, vlib_cli_command_t * cmd)
503 {
504   pg_main_t *pg = &pg_main;
505   u32 i;
506
507   if (!unformat (input, "%U",
508                  &unformat_hash_vec_string, pg->stream_index_by_name, &i))
509     return clib_error_create ("expected stream name `%U'",
510                               format_unformat_error, input);
511
512   pg_stream_del (pg, i);
513   return 0;
514 }
515
516 /* *INDENT-OFF* */
517 VLIB_CLI_COMMAND (del_stream_cli, static) = {
518   .path = "packet-generator delete",
519   .function = del_stream,
520   .short_help = "Delete stream with given name",
521 };
522 /* *INDENT-ON* */
523
524 static clib_error_t *
525 change_stream_parameters (vlib_main_t * vm,
526                           unformat_input_t * input, vlib_cli_command_t * cmd)
527 {
528   pg_main_t *pg = &pg_main;
529   pg_stream_t *s, s_new;
530   u32 stream_index = ~0;
531   clib_error_t *error;
532
533   if (unformat (input, "%U", unformat_hash_vec_string,
534                 pg->stream_index_by_name, &stream_index))
535     ;
536   else
537     return clib_error_create ("expecting stream name; got `%U'",
538                               format_unformat_error, input);
539
540   s = pool_elt_at_index (pg->streams, stream_index);
541   s_new = s[0];
542
543   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
544     {
545       if (unformat_user (input, unformat_pg_stream_parameter, &s_new))
546         ;
547
548       else
549         return clib_error_create ("unknown input `%U'",
550                                   format_unformat_error, input);
551     }
552
553   error = validate_stream (&s_new);
554   if (!error)
555     {
556       s[0] = s_new;
557       pg_stream_change (pg, s);
558     }
559
560   return error;
561 }
562
563 /* *INDENT-OFF* */
564 VLIB_CLI_COMMAND (change_stream_parameters_cli, static) = {
565   .path = "packet-generator configure",
566   .short_help = "Change packet generator stream parameters",
567   .function = change_stream_parameters,
568 };
569 /* *INDENT-ON* */
570
571 static clib_error_t *
572 pg_capture_cmd_fn (vlib_main_t * vm,
573                    unformat_input_t * input, vlib_cli_command_t * cmd)
574 {
575   clib_error_t *error = 0;
576   vnet_main_t *vnm = vnet_get_main ();
577   unformat_input_t _line_input, *line_input = &_line_input;
578   vnet_hw_interface_t *hi = 0;
579   u8 *pcap_file_name = 0;
580   u32 hw_if_index;
581   u32 is_disable = 0;
582   u32 count = ~0;
583
584   if (!unformat_user (input, unformat_line_input, line_input))
585     return 0;
586
587   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
588     {
589       if (unformat (line_input, "%U",
590                     unformat_vnet_hw_interface, vnm, &hw_if_index))
591         {
592           hi = vnet_get_hw_interface (vnm, hw_if_index);
593         }
594
595       else if (unformat (line_input, "pcap %s", &pcap_file_name))
596         ;
597       else if (unformat (line_input, "count %u", &count))
598         ;
599       else if (unformat (line_input, "disable"))
600         is_disable = 1;
601
602       else
603         {
604           error = clib_error_create ("unknown input `%U'",
605                                      format_unformat_error, line_input);
606           goto done;
607         }
608     }
609
610   if (!hi)
611     {
612       error = clib_error_return (0, "Please specify interface name");
613       goto done;
614     }
615
616   if (hi->dev_class_index != pg_dev_class.index)
617     {
618       error =
619         clib_error_return (0, "Please specify packet-generator interface");
620       goto done;
621     }
622
623   if (!pcap_file_name && is_disable == 0)
624     {
625       error = clib_error_return (0, "Please specify pcap file name");
626       goto done;
627     }
628
629
630   pg_capture_args_t _a, *a = &_a;
631
632   a->hw_if_index = hw_if_index;
633   a->dev_instance = hi->dev_instance;
634   a->is_enabled = !is_disable;
635   a->pcap_file_name = (char *) pcap_file_name;
636   a->count = count;
637
638   error = pg_capture (a);
639
640 done:
641   unformat_free (line_input);
642
643   return error;
644 }
645
646 /* *INDENT-OFF* */
647 VLIB_CLI_COMMAND (pg_capture_cmd, static) = {
648   .path = "packet-generator capture",
649   .short_help = "packet-generator capture <interface name> pcap <filename> [count <n>]",
650   .function = pg_capture_cmd_fn,
651 };
652 /* *INDENT-ON* */
653
654 static clib_error_t *
655 create_pg_if_cmd_fn (vlib_main_t * vm,
656                      unformat_input_t * input, vlib_cli_command_t * cmd)
657 {
658   pg_main_t *pg = &pg_main;
659   unformat_input_t _line_input, *line_input = &_line_input;
660   u32 if_id, gso_enabled = 0, gso_size = 0;
661   clib_error_t *error = NULL;
662
663   if (!unformat_user (input, unformat_line_input, line_input))
664     return 0;
665
666   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
667     {
668       if (unformat (line_input, "interface pg%u", &if_id))
669         ;
670       else if (unformat (line_input, "gso-enabled"))
671         {
672           gso_enabled = 1;
673           if (unformat (line_input, "gso-size %u", &gso_size))
674             ;
675           else
676             {
677               error = clib_error_create ("gso enabled but gso size missing");
678               goto done;
679             }
680         }
681       else
682         {
683           error = clib_error_create ("unknown input `%U'",
684                                      format_unformat_error, line_input);
685           goto done;
686         }
687     }
688
689   pg_interface_add_or_get (pg, if_id, gso_enabled, gso_size);
690
691 done:
692   unformat_free (line_input);
693
694   return error;
695 }
696
697 /* *INDENT-OFF* */
698 VLIB_CLI_COMMAND (create_pg_if_cmd, static) = {
699   .path = "create packet-generator",
700   .short_help = "create packet-generator interface <interface name> [gso-enabled gso-size <size>]",
701   .function = create_pg_if_cmd_fn,
702 };
703 /* *INDENT-ON* */
704
705 /* Dummy init function so that we can be linked in. */
706 static clib_error_t *
707 pg_cli_init (vlib_main_t * vm)
708 {
709   return 0;
710 }
711
712 VLIB_INIT_FUNCTION (pg_cli_init);
713
714 /*
715  * fd.io coding-style-patch-verification: ON
716  *
717  * Local Variables:
718  * eval: (c-set-style "gnu")
719  * End:
720  */