3

The following snippet will only wait for the user to hit Enter the first time; after that, it cycles through all the rest of the .pcap files without waiting for user input.

$| = 1;

while (<*.pcap>) {
    print "$_";
    <STDIN>;

    system("tcpreplay -i eth0 -M 20 $_");
}

Why doesn't this wait for user input on every loop iteration?

ThisSuitIsBlackNot
  • 23,492
  • 9
  • 63
  • 110
packetie
  • 4,839
  • 8
  • 37
  • 72
  • This works fine for me `perl -e'while ( <*.txt> ) { print; <> }'` printing one file name for each press of Enter. Can you test it on your system, please? – Borodin Apr 03 '16 at 14:31
  • I've tested this, and it works for a simple case, but definitely breaks with the `system` call to `tcpreplay`. It just skips right past the anything beyond the first pass, and when assigning it to a variable, is only defined on pass one. – stevieb Apr 03 '16 at 14:33
  • Thanks guys. There is something strange about "tcpreplay". Wonder if there is any workaround. – packetie Apr 03 '16 at 14:35
  • Does ir make a difference if you chomp $_? – sidyll Apr 03 '16 at 14:36
  • @sidyll: no, it doesn't – stevieb Apr 03 '16 at 14:38
  • $_ here doesn't a newline at the end, but you pointed out a bug here, will overwrite $_. Thanks. – packetie Apr 03 '16 at 14:39
  • Please use `use strict;` in your script. Your use of `$f = $_;` indicates that you are not using `strict`. – bart Apr 03 '16 at 14:45
  • That's true. Thanks @bart for pointing it out! – packetie Apr 03 '16 at 14:46
  • 1
    @codingFun: A bare `<>` or `` *won't* overwrite `$_` -- the value entered is simply discarded. It's only within a `while` condition like `while ( <> ) { ... }` that `$_` is used to store the input – Borodin Apr 03 '16 at 21:33
  • Thanks @Borodin for pointing it out, I was wrong. – packetie Apr 04 '16 at 23:22

2 Answers2

2

Do you care about tcpreplay output? Redirecting stdout and stderr seems to fix this problem:

system("tcpreplay -i eth0 -M 20 $_ >/dev/null 2>&1");

Alternatively, you can use this to capture tcpreplay output:

my $tcpreplay_output = `tcpreplay -i eth0 -M 20 $_ 2>&1`;
bart
  • 898
  • 4
  • 5
2

tcpreplay sets STDIN to use non-blocking I/O, which causes reads to return an error immediately if no data is available. You can see this by checking the return value of readline:

use strict;
use warnings 'all';
use 5.010;

$| = 1;

while (<*.pcap>) {
    say;
    die "readline error: $!" if ! defined <STDIN>;

    system("tcpreplay -i eth0 $_") == 0
        or die "tcpreplay failed: $?";
}

After the first call to tcpreplay, this dies with the message:

readline error: Resource temporarily unavailable at ./replay line 10, <STDIN> line 1.

This corresponds to errno EAGAIN, which read returns if a file descriptor marked as non-blocking would have to block to wait for I/O.


If your system implements fcntl, you can get around this by setting STDIN to use blocking I/O after every call to tcpreplay:

use strict;
use warnings 'all';
use 5.010;

use Fcntl;

$| = 1;

while (<*.pcap>) {
    say;
    die "readline error: $!" if ! defined <STDIN>;

    system("tcpreplay -i eth0 $_") == 0
        or die "tcpreplay failed: $?";

    # Reset STDIN to use blocking I/O
    my $flags = fcntl(STDIN, F_GETFL, 0) 
        or die "Couldn't get flags for STDIN: $!";
    fcntl(STDIN, F_SETFL, $flags & ~O_NONBLOCK) 
        or die "Couldn't set flags for STDIN: $!";
}

This is just a workaround, though; tcpreplay should be fixed.

Community
  • 1
  • 1
ThisSuitIsBlackNot
  • 23,492
  • 9
  • 63
  • 110