Line data Source code
1 : #include "../dev.h"
2 : #include "../../../shared/commands/configure/configure.h" /* CONFIGURE_CMD_INIT */
3 : #include "../../../shared/commands/run/run.h" /* fdctl_check_configure */
4 : #include "../../../../disco/net/fd_net_tile.h"
5 : #include "../../../../disco/metrics/fd_metrics.h"
6 : #include "../../../../disco/topo/fd_topob.h"
7 : #include "../../../../disco/topo/fd_cpu_topo.h"
8 : #include "../../../../util/net/fd_ip4.h"
9 :
10 : #include <stdio.h> /* printf */
11 : #include <unistd.h> /* isatty */
12 : #include <sys/ioctl.h>
13 : #include <poll.h>
14 :
15 : extern fd_topo_obj_callbacks_t * CALLBACKS[];
16 :
17 : fd_topo_run_tile_t
18 : fdctl_tile_run( fd_topo_tile_t const * tile );
19 :
20 : static void
21 0 : pktgen_topo( config_t * config ) {
22 0 : ulong net_tile_cnt = config->layout.net_tile_count;
23 :
24 0 : char const * affinity = config->development.pktgen.affinity;
25 0 : int is_auto_affinity = !strcmp( affinity, "auto" );
26 :
27 0 : ushort parsed_tile_to_cpu[ FD_TILE_MAX ];
28 0 : for( ulong i=0UL; i<FD_TILE_MAX; i++ ) parsed_tile_to_cpu[ i ] = USHORT_MAX;
29 :
30 0 : fd_topo_cpus_t cpus[1];
31 0 : fd_topo_cpus_init( cpus );
32 :
33 0 : ulong affinity_tile_cnt = 0UL;
34 0 : ulong required_tile_cnt = 3UL+net_tile_cnt;
35 0 : if( FD_LIKELY( !is_auto_affinity ) ) affinity_tile_cnt = fd_topob_parse_affinity_cstr( affinity, parsed_tile_to_cpu, 0, 1 );
36 :
37 0 : ulong tile_to_cpu[ FD_TILE_MAX ] = {0};
38 0 : for( ulong i=0UL; i<affinity_tile_cnt; i++ ) {
39 0 : ushort cpu_idx = (ushort)( parsed_tile_to_cpu[ i ] & ~FD_TOPOB_CPU_SHARED );
40 0 : if( FD_UNLIKELY( parsed_tile_to_cpu[ i ]!=USHORT_MAX && cpu_idx>=cpus->cpu_cnt ) )
41 0 : FD_LOG_ERR(( "The CPU affinity string in the configuration file under [development.pktgen.affinity] specifies a CPU index of %hu, but the system "
42 0 : "only has %lu CPUs. You should either change the CPU allocations in the affinity string, or increase the number of CPUs "
43 0 : "in the system.",
44 0 : cpu_idx, cpus->cpu_cnt ));
45 0 : tile_to_cpu[ i ] = fd_ulong_if( parsed_tile_to_cpu[ i ]==USHORT_MAX, ULONG_MAX, (ulong)parsed_tile_to_cpu[ i ] );
46 0 : }
47 0 : if( FD_LIKELY( !is_auto_affinity ) ) {
48 0 : if( FD_UNLIKELY( affinity_tile_cnt!=required_tile_cnt ) )
49 0 : FD_LOG_ERR(( "Invalid [development.pktgen.affinity]: must include exactly %lu CPUs", required_tile_cnt ));
50 0 : }
51 :
52 : /* Reset topology from scratch */
53 0 : fd_topo_t * topo = &config->topo;
54 0 : fd_topob_new( &config->topo, config->name );
55 0 : topo->max_page_size = fd_cstr_to_shmem_page_sz( config->hugetlbfs.max_page_size );
56 :
57 0 : fd_topob_wksp( topo, "metric" );
58 0 : fd_topob_wksp( topo, "metric_in" );
59 0 : fd_topos_net_tiles( topo, net_tile_cnt, &config->net, config->tiles.netlink.max_routes, config->tiles.netlink.max_peer_routes, config->tiles.netlink.max_neighbors, 0, tile_to_cpu );
60 0 : fd_topob_tile( topo, "metric", "metric", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 0, 0, 1 );
61 :
62 0 : char const * net_tile_name = fd_net_tile_name( config->net.provider );
63 :
64 0 : fd_topob_wksp( topo, "pktgen" );
65 0 : fd_topo_tile_t * pktgen_tile = fd_topob_tile( topo, "pktgen", "pktgen", "pktgen", tile_to_cpu[ topo->tile_cnt ], 0, 0, 0, 0 );
66 0 : if( FD_UNLIKELY( !fd_cstr_to_ip4_addr( config->development.pktgen.fake_dst_ip, &pktgen_tile->pktgen.fake_dst_ip ) ) ) {
67 0 : FD_LOG_ERR(( "Invalid [development.pktgen.fake_dst_ip]" ));
68 0 : }
69 0 : fd_topob_link( topo, "pktgen_out", "pktgen", 32768UL, FD_NET_MTU, 1UL );
70 0 : fd_topob_tile_out( topo, "pktgen", 0UL, "pktgen_out", 0UL );
71 0 : for( ulong i=0UL; i<net_tile_cnt; i++ ) {
72 0 : fd_topob_tile_in( topo, net_tile_name, i, "metric_in", "pktgen_out", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED );
73 :
74 : /* Create dummy RX link */
75 0 : fd_topos_net_rx_link( topo, "net_quic", i, config->net.ingress_buffer_size );
76 0 : fd_topob_tile_in( topo, "pktgen", 0UL, "metric_in", "net_quic", i, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED );
77 :
78 0 : fd_topos_net_tile_finish( topo, i );
79 0 : }
80 :
81 :
82 0 : if( FD_UNLIKELY( is_auto_affinity ) ) fd_topob_auto_layout( topo, 0 );
83 0 : topo->agave_affinity_cnt = 0;
84 0 : fd_topob_waker( topo );
85 0 : fd_topob_finish( topo, CALLBACKS );
86 0 : fd_topo_print_log( /* stdout */ 1, topo );
87 0 : }
88 :
89 : void
90 : pktgen_cmd_args( int * pargc,
91 : char *** pargv,
92 0 : args_t * args ) {
93 : /* FIXME add config options here */
94 0 : (void)pargc; (void)pargv; (void)args;
95 0 : }
96 :
97 : /* Hacky: Since the pktgen runs in the same process, use globals to
98 : share state */
99 : extern uint fd_pktgen_active;
100 :
101 : /* fd_net_stats stores the stats for a single net tile. */
102 :
103 : struct fd_net_stats {
104 : double busy_r;
105 : double rx_ok_pps;
106 : double rx_bps;
107 : double rx_drop_pps;
108 : double tx_ok_pps;
109 : double tx_bps;
110 :
111 : ulong rx_bufs_idle;
112 : ulong rx_bufs_busy;
113 : ulong tx_bufs_idle;
114 : ulong tx_bufs_busy;
115 : };
116 : typedef struct fd_net_stats fd_net_stats_t;
117 :
118 : /* fd_net_metrics_last stores recent key metrics for a single net
119 : tile, used to calculate the next net_stats. */
120 :
121 : struct fd_net_metrics_last {
122 : long ts;
123 : ulong cum_idle;
124 : ulong cum_tick;
125 : ulong rx_ok;
126 : ulong rx_byte;
127 : ulong rx_drop;
128 : ulong tx_ok;
129 : ulong tx_byte;
130 : };
131 : typedef struct fd_net_metrics_last fd_net_metrics_last_t;
132 :
133 : /* get_net_stats computes and returns per net tile stats. */
134 :
135 : static fd_net_stats_t const *
136 : get_net_stats( ulong volatile const * net_metrics[ FD_TOPO_MAX_TILES ],
137 : ulong net_tile_cnt,
138 0 : char const * provider ) {
139 :
140 0 : static fd_net_stats_t net_stats [ FD_TOPO_MAX_TILES ];
141 0 : static fd_net_metrics_last_t net_metrics_last[ FD_TOPO_MAX_TILES ];
142 :
143 0 : long now = fd_log_wallclock();
144 :
145 0 : for( ulong kind_id=0UL; kind_id<net_tile_cnt; kind_id++ ) {
146 0 : ulong volatile const * metrics = net_metrics [ kind_id ];
147 0 : fd_net_metrics_last_t * last = &net_metrics_last[ kind_id ];
148 0 : fd_net_stats_t * stats = &net_stats [ kind_id ];
149 :
150 0 : if( FD_UNLIKELY( last->ts==0L ) ) last->ts = now;
151 :
152 0 : long dt = now-last->ts;
153 0 : if( dt<=(long)10e6 ) continue;
154 :
155 0 : ulong cum_idle_now = metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_POSTFRAG ) ];
156 0 : ulong cum_tick_now = cum_idle_now;
157 0 : /* */ cum_tick_now += metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_HOUSEKEEPING ) ];
158 0 : /* */ cum_tick_now += metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_PROCESSING_HOUSEKEEPING ) ];
159 0 : /* */ cum_tick_now += metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_HOUSEKEEPING ) ];
160 0 : /* */ cum_tick_now += metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_PREFRAG ) ];
161 0 : /* */ cum_tick_now += metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_PROCESSING_PREFRAG ) ];
162 0 : /* */ cum_tick_now += metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_PREFRAG ) ];
163 0 : /* */ cum_tick_now += metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_PROCESSING_POSTFRAG ) ];
164 :
165 0 : ulong rx_ok_now = 0;
166 0 : ulong rx_byte_now = 0;
167 0 : ulong rx_drop_now = 0;
168 0 : ulong tx_ok_now = 0;
169 0 : ulong tx_byte_now = 0;
170 :
171 0 : if( 0==strcmp( provider, "xdp" ) ) {
172 0 : stats->rx_bufs_idle = metrics[ MIDX( GAUGE, NET, RX_BUFFER_IDLE ) ];
173 0 : stats->rx_bufs_busy = metrics[ MIDX( GAUGE, NET, RX_BUFFER_BUSY ) ];
174 0 : stats->tx_bufs_idle = metrics[ MIDX( GAUGE, NET, TX_BUFFER_IDLE ) ];
175 0 : stats->tx_bufs_busy = metrics[ MIDX( GAUGE, NET, TX_BUFFER_BUSY ) ];
176 :
177 0 : rx_ok_now = metrics[ MIDX( COUNTER, NET, PKT_RX ) ];
178 0 : rx_byte_now = metrics[ MIDX( COUNTER, NET, PKT_RX_BYTES ) ];
179 0 : rx_drop_now = metrics[ MIDX( COUNTER, NET, PKT_RX_FILL_RING_FULL ) ];
180 0 : /* */ rx_drop_now += metrics[ MIDX( COUNTER, NET, PKT_RX_BACKPRESSURE ) ];
181 0 : /* */ rx_drop_now += metrics[ MIDX( COUNTER, NET, XDP_RX_OTHER_DROPPED ) ];
182 0 : /* */ rx_drop_now += metrics[ MIDX( COUNTER, NET, XDP_RX_INVALID_DESCRIPTOR ) ];
183 0 : /* */ rx_drop_now += metrics[ MIDX( COUNTER, NET, XDP_RX_RING_FULL ) ];
184 0 : tx_ok_now = metrics[ MIDX( COUNTER, NET, PKT_TX_COMPLETED ) ];
185 0 : tx_byte_now = metrics[ MIDX( COUNTER, NET, PKT_TX_BYTES ) ];
186 :
187 0 : } else if( 0==strcmp( provider, "mlx5" ) ) {
188 0 : stats->rx_bufs_idle = metrics[ MIDX( GAUGE, MLX5, RX_BUFFER_IDLE ) ];
189 0 : stats->rx_bufs_busy = metrics[ MIDX( GAUGE, MLX5, RX_BUFFER_BUSY ) ];
190 0 : stats->tx_bufs_idle = metrics[ MIDX( GAUGE, MLX5, TX_BUFFER_IDLE ) ];
191 0 : stats->tx_bufs_busy = metrics[ MIDX( GAUGE, MLX5, TX_BUFFER_BUSY ) ];
192 :
193 0 : rx_ok_now = metrics[ MIDX( COUNTER, MLX5, PKT_RX ) ];
194 0 : rx_byte_now = metrics[ MIDX( COUNTER, MLX5, PKT_RX_BYTES ) ];
195 0 : rx_drop_now = metrics[ MIDX( COUNTER, MLX5, PKT_RX_MALFORMED ) ];
196 0 : /* */ rx_drop_now += metrics[ MIDX( COUNTER, MLX5, PKT_RX_ROUTE_FAIL ) ];
197 0 : tx_ok_now = metrics[ MIDX( COUNTER, MLX5, PKT_TX_COMPLETED ) ];
198 0 : tx_byte_now = metrics[ MIDX( COUNTER, MLX5, PKT_TX_BYTES ) ];
199 0 : }
200 :
201 :
202 0 : ulong cum_idle_delta = cum_idle_now-last->cum_idle;
203 0 : ulong cum_tick_delta = cum_tick_now-last->cum_tick;
204 0 : ulong rx_ok_delta = rx_ok_now -last->rx_ok;
205 0 : ulong rx_byte_delta = rx_byte_now -last->rx_byte;
206 0 : ulong rx_drop_delta = rx_drop_now -last->rx_drop;
207 0 : ulong tx_ok_delta = tx_ok_now -last->tx_ok;
208 0 : ulong tx_byte_delta = tx_byte_now -last->tx_byte;
209 :
210 0 : stats->busy_r = 1.0 - ( (double)cum_idle_delta / (double)cum_tick_delta );
211 0 : stats->rx_ok_pps = 1e9*( (double)rx_ok_delta /(double)dt );
212 0 : stats->rx_bps = 8e9*( (double)rx_byte_delta/(double)dt );
213 0 : stats->rx_drop_pps = 1e9*( (double)rx_drop_delta/(double)dt );
214 0 : stats->tx_ok_pps = 1e9*( (double)tx_ok_delta /(double)dt );
215 0 : stats->tx_bps = 8e9*( (double)tx_byte_delta/(double)dt );
216 :
217 0 : last->ts = now;
218 0 : last->cum_idle = cum_idle_now;
219 0 : last->cum_tick = cum_tick_now;
220 0 : last->rx_ok = rx_ok_now;
221 0 : last->rx_byte = rx_byte_now;
222 0 : last->rx_drop = rx_drop_now;
223 0 : last->tx_ok = tx_ok_now;
224 0 : last->tx_byte = tx_byte_now;
225 0 : }
226 :
227 0 : return net_stats;
228 0 : }
229 :
230 : /* render_status prints statistics at the top of the screen.
231 : Should be called at a low rate (~500ms). */
232 :
233 : static void
234 : render_status( ulong volatile const * net_metrics[ FD_TOPO_MAX_TILES ],
235 : ulong net_tile_cnt,
236 0 : char const * provider ) {
237 0 : fputs( "\0337" /* save cursor position */
238 0 : "\033[H" /* move cursor to (0,0) */
239 0 : "\033[2K\n", /* create an empty line to avoid spamming look back buffer */
240 0 : stdout );
241 0 : printf( "\033[2K" "[Firedancer pktgen] mode=%s\n",
242 0 : FD_VOLATILE_CONST( fd_pktgen_active ) ? "send+recv" : "recv" );
243 :
244 0 : fd_net_stats_t const * stats = get_net_stats( net_metrics, net_tile_cnt, provider );
245 :
246 0 : fd_net_stats_t summary = {0};
247 0 : for( ulong i=0UL; i<net_tile_cnt; i++ ) {
248 0 : summary.busy_r += stats[ i ].busy_r;
249 0 : summary.rx_ok_pps += stats[ i ].rx_ok_pps;
250 0 : summary.rx_bps += stats[ i ].rx_bps;
251 0 : summary.rx_drop_pps += stats[ i ].rx_drop_pps;
252 0 : summary.tx_ok_pps += stats[ i ].tx_ok_pps;
253 0 : summary.tx_bps += stats[ i ].tx_bps;
254 0 : summary.rx_bufs_idle += stats[ i ].rx_bufs_idle;
255 0 : summary.rx_bufs_busy += stats[ i ].rx_bufs_busy;
256 0 : summary.tx_bufs_idle += stats[ i ].tx_bufs_idle;
257 0 : summary.tx_bufs_busy += stats[ i ].tx_bufs_busy;
258 0 : }
259 0 : summary.busy_r /= (double)net_tile_cnt;
260 :
261 0 : printf( "\033[2K" "Overall stats:\n" );
262 :
263 : /* Render packet per second rates */
264 0 : printf( "\033[2K" " Net busy: %.2f%%\n"
265 0 : "\033[2K" " RX ok: %10.3e pps %10.3e bps\n"
266 0 : "\033[2K" " RX drop: %10.3e pps\n"
267 0 : "\033[2K" " TX ok: %10.3e pps %10.3e bps\n"
268 0 : "\033[2K" " RX bufs: %6lu idle %6lu busy\n"
269 0 : "\033[2K" " TX bufs: %6lu idle %6lu busy\n",
270 0 : 100.*summary.busy_r,
271 0 : summary.rx_ok_pps, summary.rx_bps,
272 0 : summary.rx_drop_pps,
273 0 : summary.tx_ok_pps, summary.tx_bps,
274 0 : summary.rx_bufs_idle, summary.rx_bufs_busy,
275 0 : summary.tx_bufs_idle, summary.tx_bufs_busy );
276 :
277 0 : printf( "\033[2K" "Individual net tiles:\n" );
278 :
279 0 : for( ulong i=0UL; i<net_tile_cnt; i++ ) {
280 0 : printf( "\033[2K" " net:%-3lu busy %6.2f%% | RX(ok) %10.3e pps | TX(ok) %10.3e pps\n",
281 0 : i,
282 0 : 100.*stats[ i ].busy_r,
283 0 : stats[ i ].rx_ok_pps,
284 0 : stats[ i ].tx_ok_pps );
285 0 : }
286 :
287 0 : fputs( "\0338", stdout ); /* restore cursor position */
288 0 : fflush( stdout );
289 0 : }
290 :
291 : /* FIXME fixup screen on window size changes */
292 :
293 : void
294 : pktgen_cmd_fn( args_t * args FD_PARAM_UNUSED,
295 0 : config_t * config ) {
296 0 : fd_topo_t * topo = &config->topo;
297 0 : ulong net_tile_cnt = config->layout.net_tile_count;
298 0 : if( FD_UNLIKELY( net_tile_cnt>FD_TOPO_MAX_TILES ) ) {
299 0 : FD_LOG_ERR(( "more net tiles (%lu) than max num tiles allowed (%lu)", net_tile_cnt, FD_TOPO_MAX_TILES ));
300 0 : }
301 :
302 0 : fd_topo_tile_t * metric_tile = &topo->tiles[ fd_topo_find_tile( topo, "metric", 0UL ) ];
303 :
304 0 : fd_topo_tile_t * net_tiles [ FD_TOPO_MAX_TILES ];
305 0 : ulong volatile const * net_metrics[ FD_TOPO_MAX_TILES ];
306 :
307 0 : ushort const listen_port = 9000;
308 0 : config->tiles.quic.regular_transaction_listen_port = listen_port;
309 0 : for( ulong kind_id=0UL; kind_id<net_tile_cnt; kind_id++ ) {
310 0 : net_tiles[ kind_id ] = &topo->tiles[ fd_topo_find_tile( topo, fd_net_tile_name( config->net.provider ), kind_id ) ];
311 0 : net_tiles[ kind_id ]->net.legacy_transaction_listen_port = listen_port;
312 0 : }
313 :
314 0 : if( FD_UNLIKELY( !fd_cstr_to_ip4_addr( config->tiles.metric.prometheus_listen_address, &metric_tile->metric.prometheus_listen_addr ) ) )
315 0 : FD_LOG_ERR(( "failed to parse prometheus listen address `%s`", config->tiles.metric.prometheus_listen_address ));
316 0 : metric_tile->metric.prometheus_listen_port = config->tiles.metric.prometheus_listen_port;
317 :
318 0 : configure_stage( &fd_cfg_stage_sysctl, CONFIGURE_CMD_INIT, config );
319 0 : configure_stage( &fd_cfg_stage_hugetlbfs, CONFIGURE_CMD_INIT, config );
320 0 : configure_stage( &fd_cfg_stage_bonding, CONFIGURE_CMD_INIT, config );
321 0 : configure_stage( &fd_cfg_stage_ethtool_channels, CONFIGURE_CMD_INIT, config );
322 0 : configure_stage( &fd_cfg_stage_ethtool_offloads, CONFIGURE_CMD_INIT, config );
323 0 : configure_stage( &fd_cfg_stage_sysfs_poll, CONFIGURE_CMD_INIT, config );
324 :
325 0 : fdctl_check_configure( config );
326 : /* FIXME this allocates lots of memory unnecessarily */
327 0 : initialize_workspaces( config );
328 0 : initialize_stacks( config );
329 0 : if( 0==strcmp( config->net.provider, "xdp" ) ) {
330 0 : fd_topo_install_xdp_simple( &config->topo, config->net.bind_address_parsed );
331 0 : }
332 0 : fd_topo_join_workspaces( topo, FD_SHMEM_JOIN_MODE_READ_WRITE, FD_TOPO_CORE_DUMP_LEVEL_DISABLED );
333 0 : if( 0==strcmp( config->net.provider, "mlx5" ) ) {
334 0 : fd_topo_install_mlx5( topo, NULL );
335 0 : }
336 :
337 : /* FIXME allow running sandboxed/multiprocess */
338 0 : fd_topo_run_single_process( topo, 2, config->uid, config->gid, fdctl_tile_run );
339 :
340 0 : for( ulong kind_id=0UL; kind_id<net_tile_cnt; kind_id++ ) {
341 0 : net_metrics[ kind_id ] = fd_metrics_tile( net_tiles[ kind_id ]->metrics );
342 0 : }
343 :
344 : /* Don't attempt to render TTY */
345 0 : if( !isatty( STDOUT_FILENO ) ) {
346 0 : puts( "stdout is not a tty, not taking commands" );
347 0 : FD_VOLATILE( fd_pktgen_active ) = 1;
348 0 : for(;;) pause();
349 0 : return;
350 0 : }
351 :
352 : /* Clear screen */
353 0 : struct winsize w;
354 0 : if( FD_UNLIKELY( 0!=ioctl( STDOUT_FILENO, TIOCGWINSZ, &w ) ) ) {
355 0 : FD_LOG_WARNING(( "ioctl(STDOUT_FILENO,TIOCGWINSZ) failed" ));
356 0 : } else {
357 0 : for( ulong i=0UL; i<w.ws_row; i++ ) putc( '\n', stdout );
358 0 : }
359 :
360 : /* Simple REPL loop */
361 0 : puts( "Running fddev pktgen" );
362 0 : printf( "%s socket listening on port %u\n", config->net.provider, (uint)listen_port );
363 0 : puts( "Available commands: start, stop, quit" );
364 0 : puts( "" );
365 0 : char input[ 256 ] = {0};
366 0 : for(;;) {
367 0 : render_status( net_metrics, net_tile_cnt, config->net.provider );
368 0 : fputs( "pktgen> ", stdout );
369 0 : fflush( stdout );
370 :
371 0 : for(;;) {
372 0 : struct pollfd fds[1] = {{ .fd=STDIN_FILENO, .events=POLLIN }};
373 0 : int poll_res = poll( fds, 1, 500 );
374 0 : if( poll_res==0 ) {
375 0 : render_status( net_metrics, net_tile_cnt, config->net.provider );
376 0 : continue;
377 0 : } else if( poll_res>0 ) {
378 0 : break;
379 0 : } else {
380 0 : FD_LOG_ERR(( "poll(STDIN_FILENO) failed" ));
381 0 : break;
382 0 : }
383 0 : }
384 :
385 0 : if( fgets( input, sizeof(input), stdin )==NULL ) {
386 0 : putc( '\n', stdout );
387 0 : break;
388 0 : }
389 0 : input[ strcspn( input, "\n" ) ] = '\0';
390 0 : input[ sizeof(input)-1 ] = '\0';
391 :
392 0 : if( !input[0] ) {
393 : /* No command */
394 0 : } else if( !strcmp( input, "exit" ) || !strcmp( input, "quit" ) ) {
395 0 : break;
396 0 : } else if( !strcmp( input, "start" ) ) {
397 0 : FD_VOLATILE( fd_pktgen_active ) = 1U;
398 0 : } else if( !strcmp( input, "stop" ) ) {
399 0 : FD_VOLATILE( fd_pktgen_active ) = 0U;
400 0 : } else {
401 0 : fputs( "Unknown command\n", stdout );
402 0 : }
403 0 : }
404 0 : puts( "Exiting" );
405 0 : }
406 :
407 : action_t fd_action_pktgen = {
408 : .name = "pktgen",
409 : .topo = pktgen_topo,
410 : .args = pktgen_cmd_args,
411 : .fn = pktgen_cmd_fn,
412 : .perm = dev_cmd_perm,
413 : .description = "Flood interface with invalid Ethernet frames"
414 : };
|