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* */
51VLIB_CLI_COMMAND (vlib_cli_pg_command, static) = {
52  .path = "packet-generator",
53  .short_help = "Packet generator commands",
54};
55/* *INDENT-ON* */
56
57void
58pg_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
80clib_error_t *
81pg_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
112static clib_error_t *
113enable_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
135doit:
136  pg_enable_disable (stream_index, is_enable);
137
138  return 0;
139}
140
141/* *INDENT-OFF* */
142VLIB_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* */
151VLIB_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
159static u8 *
160format_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
184static u8 *
185format_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
224static clib_error_t *
225show_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
253done:
254  return 0;
255}
256
257/* *INDENT-OFF* */
258VLIB_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
265static clib_error_t *
266pg_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
289static uword
290unformat_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
318static clib_error_t *
319validate_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
336static clib_error_t *
337new_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, "buffer-flags %U",
380			 unformat_vnet_buffer_flags, &s.buffer_flags))
381	;
382
383      else if (unformat (input, "node %U",
384			 unformat_vlib_node, vm, &s.node_index))
385	;
386      else if (unformat (input, "maxframe %u", &maxframe))
387	s.n_max_frame = s.n_max_frame < maxframe ? s.n_max_frame : maxframe;
388      else if (unformat (input, "worker %u", &s.worker_index))
389	;
390
391      else if (unformat (input, "interface %U",
392			 unformat_vnet_sw_interface, vnm,
393			 &s.sw_if_index[VLIB_RX]))
394	;
395      else if (unformat (input, "tx-interface %U",
396			 unformat_vnet_sw_interface, vnm,
397			 &s.sw_if_index[VLIB_TX]))
398	;
399
400      else if (unformat (input, "pcap %s", &pcap_file_name))
401	;
402
403      else if (!sub_input_given
404	       && unformat (input, "data %U", unformat_input, &sub_input))
405	sub_input_given++;
406
407      else if (unformat_user (input, unformat_pg_stream_parameter, &s))
408	;
409
410      else
411	{
412	  error = clib_error_create ("unknown input `%U'",
413				     format_unformat_error, input);
414	  goto done;
415	}
416    }
417
418  if (!sub_input_given && !pcap_file_name)
419    {
420      error = clib_error_create ("no packet data given");
421      goto done;
422    }
423
424  if (s.node_index == ~0)
425    {
426      if (pcap_file_name != 0)
427	{
428	  vlib_node_t *n =
429	    vlib_get_node_by_name (vm, (u8 *) "ethernet-input");
430	  s.node_index = n->index;
431	}
432      else
433	{
434	  error = clib_error_create ("output interface or node not given");
435	  goto done;
436	}
437    }
438
439  {
440    pg_node_t *n;
441
442    if (s.node_index < vec_len (pg->nodes))
443      n = pg->nodes + s.node_index;
444    else
445      n = 0;
446
447    if (s.worker_index >= vlib_num_workers ())
448      s.worker_index = 0;
449
450    if (pcap_file_name != 0)
451      {
452	error = pg_pcap_read (&s, pcap_file_name);
453	if (error)
454	  goto done;
455	vec_free (pcap_file_name);
456      }
457
458    else if (n && n->unformat_edit
459	     && unformat_user (&sub_input, n->unformat_edit, &s))
460      ;
461
462    else if (!unformat_user (&sub_input, unformat_pg_payload, &s))
463      {
464	error = clib_error_create
465	  ("failed to parse packet data from `%U'",
466	   format_unformat_error, &sub_input);
467	goto done;
468      }
469  }
470
471  error = validate_stream (&s);
472  if (error)
473    return error;
474
475  pg_stream_add (pg, &s);
476  return 0;
477
478done:
479  pg_stream_free (&s);
480  unformat_free (&sub_input);
481  return error;
482}
483
484/* *INDENT-OFF* */
485VLIB_CLI_COMMAND (new_stream_cli, static) = {
486  .path = "packet-generator new",
487  .function = new_stream,
488  .short_help = "Create packet generator stream",
489  .long_help =
490  "Create packet generator stream\n"
491  "\n"
492  "Arguments:\n"
493  "\n"
494  "name STRING          sets stream name\n"
495  "interface STRING     interface for stream output \n"
496  "node NODE-NAME       node for stream output\n"
497  "data STRING          specifies packet data\n"
498  "pcap FILENAME        read packet data from pcap file\n"
499  "rate PPS             rate to transfer packet data\n"
500  "maxframe NPKTS       maximum number of packets per frame\n",
501};
502/* *INDENT-ON* */
503
504static clib_error_t *
505del_stream (vlib_main_t * vm,
506	    unformat_input_t * input, vlib_cli_command_t * cmd)
507{
508  pg_main_t *pg = &pg_main;
509  u32 i;
510
511  if (!unformat (input, "%U",
512		 &unformat_hash_vec_string, pg->stream_index_by_name, &i))
513    return clib_error_create ("expected stream name `%U'",
514			      format_unformat_error, input);
515
516  pg_stream_del (pg, i);
517  return 0;
518}
519
520/* *INDENT-OFF* */
521VLIB_CLI_COMMAND (del_stream_cli, static) = {
522  .path = "packet-generator delete",
523  .function = del_stream,
524  .short_help = "Delete stream with given name",
525};
526/* *INDENT-ON* */
527
528static clib_error_t *
529change_stream_parameters (vlib_main_t * vm,
530			  unformat_input_t * input, vlib_cli_command_t * cmd)
531{
532  pg_main_t *pg = &pg_main;
533  pg_stream_t *s, s_new;
534  u32 stream_index = ~0;
535  clib_error_t *error;
536
537  if (unformat (input, "%U", unformat_hash_vec_string,
538		pg->stream_index_by_name, &stream_index))
539    ;
540  else
541    return clib_error_create ("expecting stream name; got `%U'",
542			      format_unformat_error, input);
543
544  s = pool_elt_at_index (pg->streams, stream_index);
545  s_new = s[0];
546
547  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
548    {
549      if (unformat_user (input, unformat_pg_stream_parameter, &s_new))
550	;
551
552      else
553	return clib_error_create ("unknown input `%U'",
554				  format_unformat_error, input);
555    }
556
557  error = validate_stream (&s_new);
558  if (!error)
559    {
560      s[0] = s_new;
561      pg_stream_change (pg, s);
562    }
563
564  return error;
565}
566
567/* *INDENT-OFF* */
568VLIB_CLI_COMMAND (change_stream_parameters_cli, static) = {
569  .path = "packet-generator configure",
570  .short_help = "Change packet generator stream parameters",
571  .function = change_stream_parameters,
572};
573/* *INDENT-ON* */
574
575static clib_error_t *
576pg_capture_cmd_fn (vlib_main_t * vm,
577		   unformat_input_t * input, vlib_cli_command_t * cmd)
578{
579  clib_error_t *error = 0;
580  vnet_main_t *vnm = vnet_get_main ();
581  unformat_input_t _line_input, *line_input = &_line_input;
582  vnet_hw_interface_t *hi = 0;
583  u8 *pcap_file_name = 0;
584  u32 hw_if_index;
585  u32 is_disable = 0;
586  u32 count = ~0;
587
588  if (!unformat_user (input, unformat_line_input, line_input))
589    return 0;
590
591  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
592    {
593      if (unformat (line_input, "%U",
594		    unformat_vnet_hw_interface, vnm, &hw_if_index))
595	{
596	  hi = vnet_get_hw_interface (vnm, hw_if_index);
597	}
598
599      else if (unformat (line_input, "pcap %s", &pcap_file_name))
600	;
601      else if (unformat (line_input, "count %u", &count))
602	;
603      else if (unformat (line_input, "disable"))
604	is_disable = 1;
605
606      else
607	{
608	  error = clib_error_create ("unknown input `%U'",
609				     format_unformat_error, line_input);
610	  goto done;
611	}
612    }
613
614  if (!hi)
615    {
616      error = clib_error_return (0, "Please specify interface name");
617      goto done;
618    }
619
620  if (hi->dev_class_index != pg_dev_class.index)
621    {
622      error =
623	clib_error_return (0, "Please specify packet-generator interface");
624      goto done;
625    }
626
627  if (!pcap_file_name && is_disable == 0)
628    {
629      error = clib_error_return (0, "Please specify pcap file name");
630      goto done;
631    }
632
633
634  pg_capture_args_t _a, *a = &_a;
635
636  a->hw_if_index = hw_if_index;
637  a->dev_instance = hi->dev_instance;
638  a->is_enabled = !is_disable;
639  a->pcap_file_name = (char *) pcap_file_name;
640  a->count = count;
641
642  error = pg_capture (a);
643
644done:
645  unformat_free (line_input);
646
647  return error;
648}
649
650/* *INDENT-OFF* */
651VLIB_CLI_COMMAND (pg_capture_cmd, static) = {
652  .path = "packet-generator capture",
653  .short_help = "packet-generator capture <interface name> pcap <filename> [count <n>]",
654  .function = pg_capture_cmd_fn,
655};
656/* *INDENT-ON* */
657
658static clib_error_t *
659create_pg_if_cmd_fn (vlib_main_t * vm,
660		     unformat_input_t * input, vlib_cli_command_t * cmd)
661{
662  pg_main_t *pg = &pg_main;
663  unformat_input_t _line_input, *line_input = &_line_input;
664  u32 if_id, gso_enabled = 0, gso_size = 0;
665  clib_error_t *error = NULL;
666
667  if (!unformat_user (input, unformat_line_input, line_input))
668    return 0;
669
670  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
671    {
672      if (unformat (line_input, "interface pg%u", &if_id))
673	;
674      else if (unformat (line_input, "gso-enabled"))
675	{
676	  gso_enabled = 1;
677	  if (unformat (line_input, "gso-size %u", &gso_size))
678	    ;
679	  else
680	    {
681	      error = clib_error_create ("gso enabled but gso size missing");
682	      goto done;
683	    }
684	}
685      else
686	{
687	  error = clib_error_create ("unknown input `%U'",
688				     format_unformat_error, line_input);
689	  goto done;
690	}
691    }
692
693  pg_interface_add_or_get (pg, if_id, gso_enabled, gso_size);
694
695done:
696  unformat_free (line_input);
697
698  return error;
699}
700
701/* *INDENT-OFF* */
702VLIB_CLI_COMMAND (create_pg_if_cmd, static) = {
703  .path = "create packet-generator",
704  .short_help = "create packet-generator interface <interface name> [gso-enabled gso-size <size>]",
705  .function = create_pg_if_cmd_fn,
706};
707/* *INDENT-ON* */
708
709/* Dummy init function so that we can be linked in. */
710static clib_error_t *
711pg_cli_init (vlib_main_t * vm)
712{
713  return 0;
714}
715
716VLIB_INIT_FUNCTION (pg_cli_init);
717
718/*
719 * fd.io coding-style-patch-verification: ON
720 *
721 * Local Variables:
722 * eval: (c-set-style "gnu")
723 * End:
724 */
725