#
# $Id: Dump.pm,v 1.3.2.17 2006/11/15 19:32:29 gomor Exp $
#
package Net::Packet::Dump;
use strict;
use warnings;
use Carp;

require Class::Gomor::Array;
our @ISA = qw(Class::Gomor::Array);

use Net::Packet::Env qw($Env);
require Net::Packet::Frame;
use Net::Packet::Utils qw(getRandom32bitsInt);
use Net::Packet::Consts qw(:dump :layer);

use Net::Pcap;
use Time::HiRes qw(gettimeofday);
use Storable qw(lock_store lock_retrieve);

our @AS = qw(
   dev
   env
   file
   filter
   overwrite
   timeoutOnNext
   timeout
   promisc
   link
   nextFrame
   isRunning
   unlinkOnClean
   noStore
   noLayerWipe
   mode
   keepTimestamp
   _pid
   _pcapd
   _dumper
   _stats
   _firstTime
   _sName
   _sDataAwaiting
);
our @AA = qw(
   frames
);
our @AO = qw(
   framesSorted
);
__PACKAGE__->cgBuildIndices;
__PACKAGE__->cgBuildAccessorsScalar(\@AS);
__PACKAGE__->cgBuildAccessorsArray(\@AA);

no strict 'vars';

BEGIN {
   my $osname = {
      cygwin  => \&_killTcpdumpWin32,
      MSWin32 => \&_killTcpdumpWin32,
   };

   *_killTcpdump = $osname->{$^O} || \&_killTcpdumpOther;
}

sub new {
   my $self = shift->SUPER::new(
      dev        => $Env->dev,
      env        => $Env,
      file       => "netpacket-tmp-$$.@{[getRandom32bitsInt()]}.pcap",
      filter     => '',
      overwrite  => 0,
      timeout    => 0,
      promisc    => 0,
      timeoutOnNext  => 3,
      isRunning      => 0,
      unlinkOnClean  => 1,
      noStore        => 0,
      noLayerWipe    => 0,
      framesSorted   => {},
      frames         => [],
      mode           => NP_DUMP_MODE_ONLINE,
      keepTimestamp  => 0,
      _sDataAwaiting => 0,
      _sName         => "netpacket-tmp-$$.@{[getRandom32bitsInt()]}.storable",
      @_,
   );

   unless ($self->[$__file]) {
      confess("You MUST set `file' attribute\n");
   }

   $Env->dump($self) unless $Env->noDumpAutoSet;

   $self;
}

sub isModeOnline  { shift->[$__mode] eq NP_DUMP_MODE_ONLINE  }
sub isModeOffline { shift->[$__mode] eq NP_DUMP_MODE_OFFLINE }
sub isModeWriter  { shift->[$__mode] eq NP_DUMP_MODE_WRITER  }

sub start {
   my $self = shift;

   $self->cgDebugPrint(1, 'will run in mode: '.$self->mode);

   $self->[$__isRunning] = 1;

   if ($self->isModeOnline) {
      if (-f $self->[$__file] && ! $self->[$__overwrite]) {
         croak("We will not overwrite a file by default. Use `overwrite' ".
               "attribute to do it\n");
      }
      $self->_sStore(0);
      $self->_waitFileSize($self->[$___sName]);
      $self->_startTcpdump;
      $self->_openFileOffline;
   }
   elsif ($self->isModeOffline) {
      if (! -f $self->[$__file]) {
         croak("File does not exists: ".$self->[$__file]."\n");
      }
      $self->_openFileOffline;
      $self->_setFilter;
   }
   elsif ($self->isModeWriter) {
      if (-f $self->[$__file] && ! $self->[$__overwrite]) {
         croak("We will not overwrite a file by default. Use `overwrite' ".
               "attribute to do it\n");
      }
      $self->_openFileWriter;
   }

   1;
}

sub stop {
   my $self = shift;

   return unless $self->[$__isRunning];
   return if     $self->isSon;

   if ($self->isModeOnline) {
      $self->_killTcpdump;
      $self->[$___pid] = undef;
      if ($self->[$___sName] && -f $self->[$___sName]) {
         unlink($self->[$___sName]);
      }
   }
   elsif ($self->isModeWriter) {
      Net::Pcap::dump_close($self->[$___dumper]);
   }
   elsif ($self->isModeOffline) {
      # Nothing to do here
   }

   Net::Pcap::close($self->[$___pcapd]);
   $self->[$__isRunning] = 0;

   1;
}

sub isFather { shift->[$___pid] ? 1 : 0 }
sub isSon    { shift->[$___pid] ? 0 : 1 }

sub _sStore {
   lock_store(\$_[1], $_[0]->[$___sName])
      or carp("@{[(caller(0))[3]]}: lock_store: @{[$_[0]->[$___sName]]}: $!\n");
}
sub _sRetrieve { ${lock_retrieve(shift->[$___sName])} }

sub _sonPrintStats {
   my $self = shift;

   my $stats = $self->getStats;
   Net::Pcap::breakloop($self->[$___pcapd]);
   Net::Pcap::close($self->[$___pcapd]);

   $self->cgDebugPrint(1, 'Frames received  : '.$stats->{ps_recv});
   $self->cgDebugPrint(1, 'Frames dropped   : '.$stats->{ps_drop});
   $self->cgDebugPrint(1, 'Frames if dropped: '.$stats->{ps_ifdrop});
   exit(0);
}

sub _waitFile {
   my $self = shift;
   my ($file) = @_;
   my $startTime = gettimeofday();
   my $thisTime  = $startTime;
   while (! -f $file) {
      if ($thisTime - $startTime > 10) {
         croak("@{[(caller(0))[3]]}: too long for file creation: $file\n")
      }
      $thisTime = gettimeofday();
   }
}

sub _waitFileSize {
   my $self = shift;
   my ($file) = @_;

   $self->_waitFile($file);

   my $startTime = gettimeofday();
   my $thisTime  = $startTime;
   while (! ((stat($file))[7] > 0)) {
      if ($thisTime - $startTime > 10) {
         $self->clean;
         croak("@{[(caller(0))[3]]}: too long for file creation2: $file\n")
      }
      $thisTime = gettimeofday();
   }
}

sub _startTcpdump {
   my $self = shift;

   my $err;
   my $pd = Net::Pcap::open_live(
      $self->[$__dev],
      1514,
      $self->[$__promisc],
      1000,
      \$err,
   );
   unless ($pd) {
      croak("@{[(caller(0))[3]]}: open_live: $err\n");
   }

   my $net  = 0;
   my $mask = 0;
   Net::Pcap::lookupnet($self->[$__dev], \$net, \$mask, \$err);
   if ($err) {
      carp("@{[(caller(0))[3]]}: lookupnet: $err\n");
   }

   my $fcode;
   if (Net::Pcap::compile($pd, \$fcode, $self->[$__filter], 0, $mask) < 0) {
      croak("@{[(caller(0))[3]]}: compile: ". Net::Pcap::geterr($pd). "\n");
   }

   if (Net::Pcap::setfilter($pd, $fcode) < 0) {
      croak("@{[(caller(0))[3]]}: setfilter: ". Net::Pcap::geterr($pd). "\n");
   }

   my $p = Net::Pcap::dump_open($pd, $self->[$__file]);
   unless ($p) {
      croak("@{[(caller(0))[3]]}: dump_open: ". Net::Pcap::geterr($pd). "\n");
   }
   Net::Pcap::dump_flush($p);

   $SIG{CHLD} = 'IGNORE';

   my $pid = fork();
   croak("@{[(caller(0))[3]]}: fork: $!\n") unless defined $pid;
   if ($pid) {
      $self->[$___pid] = $pid;
      return 1;
   }
   else {
      $self->[$___pcapd] = $pd;
      $SIG{INT}  = sub { $self->_sonPrintStats };
      $SIG{TERM} = sub { $self->_sonPrintStats };
      $self->cgDebugPrint(1, "dev:    [@{[$self->[$__dev]]}]\n".
                             "file:   [@{[$self->[$__file]]}]\n".
                             "filter: [@{[$self->[$__filter]]}]");
      Net::Pcap::loop($pd, -1, \&_tcpdumpCallback, [ $p, $self ]);
      Net::Pcap::close($pd);
      exit(0);
   }
}

sub _tcpdumpCallback {
   my ($data, $hdr, $pkt) = @_;
   my $p    = $data->[0];
   my $self = $data->[1];

   Net::Pcap::dump($p, $hdr, $pkt);
   Net::Pcap::dump_flush($p);

   my $n = $self->_sRetrieve;
   $self->_sStore(++$n);
}

sub _killTcpdumpWin32 {
   my $self = shift;
   return unless $self->[$___pid];
   kill('KILL', $self->[$___pid]);
}

sub _killTcpdumpOther {
   my $self = shift;
   return unless $self->[$___pid];
   kill('TERM', $self->[$___pid]);
}

sub clean {
   my $self = shift;

   if ($self->isModeOnline) {
      if ($self->[$__unlinkOnClean]
      &&  $self->[$__file] && -f $self->[$__file]) {
         unlink($self->[$__file]);
         $self->cgDebugPrint(1, "@{[$self->[$__file]]} removed");
      }
   }

   if ($self->[$___sName] && -f $self->[$___sName]) {
      unlink($self->[$___sName]);
   }

   1;
}

sub getStats {
   my $self = shift;

   unless ($self->[$___pcapd]) {
      carp("@{[(caller(0))[3]]}: unable to get stats, no pcap descriptor ".
           "opened\n");
      return undef;
   }
   
   my %stats;
   Net::Pcap::stats($self->[$___pcapd], \%stats);
   $self->[$___stats] = \%stats;
   \%stats;
}

sub flush {
   my $self = shift;
   $self->[$__frames]       = [];
   $self->[$__framesSorted] = {};
}

sub _setFilter {
   my $self = shift;
   my $str = $self->[$__filter];

   return unless $str;

   my ($net, $mask, $err);
   Net::Pcap::lookupnet($self->[$__dev], \$net, \$mask, \$err);
   if ($err) {
      croak("@{[(caller(0))[3]]}: Net::Pcap::lookupnet: @{[$self->[$__dev]]}: ".
            "$err\n");
   }

   my $filter;
   Net::Pcap::compile($self->[$___pcapd], \$filter, $str, 0,
                      $mask);
   unless ($filter) {
      croak("@{[(caller(0))[3]]}: Net::Pcap::compile: error\n");
   }

   Net::Pcap::setfilter($self->[$___pcapd], $filter);
}

sub _openFileOffline {
   my $self = shift;

   my $err;
   $self->[$___pcapd] = Net::Pcap::open_offline($self->[$__file], \$err);
   unless ($self->[$___pcapd]) {
      croak("@{[(caller(0))[3]]}: Net::Pcap::open_offline: ".
            "@{[$self->[$__file]]}: $err\n");
   }

   $self->[$__link] = Net::Pcap::datalink($self->[$___pcapd]);
}

sub _getPcapHeader {
   my $self = shift;
   # 24 bytes header of a DLT_RAW pcap file
   "\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00".
   "\x00\x00\x00\x00\xdc\x05\x00\x00\x0c\x00\x00\x00";
}

sub _openFileWriter {
   my $self = shift;
   my $file = $self->[$__file];

   my $hdr = $self->_getPcapHeader;
   open(my $fh, '>', $file)
      or croak("@{[(caller(0))[3]]}: open: $file: $!\n");
   syswrite($fh, $hdr, length($hdr));
   close($fh);

   my $err;
   my $pcapd = Net::Pcap::open_offline($file, \$err);
   unless ($pcapd) {
      croak("@{[(caller(0))[3]]}: Net::Pcap::open_offline: ".
            "$file: $err\n");
   }
   $self->[$___pcapd] = $pcapd;

   $self->[$___dumper] = Net::Pcap::dump_open($pcapd, $file);
   unless ($self->[$___dumper]) {
      croak("@{[(caller(0))[3]]}: Net::Pcap::dump_open: ".
            Net::Pcap::geterr($pcapd)."\n");
   }

   1;
}

sub _addToFramesSorted {
   my $self = shift;
   my ($frame) = @_;
   if (! $self->[$__env]->doFrameReturnList) {
      $self->framesSorted($frame);
      push @{$self->[$__frames]}, $frame;
   }
   else {
      for my $f (@$frame) {
         $self->framesSorted($f);
         push @{$self->[$__frames]}, $f;
      }
   }
}

sub _getTimestamp {
   my $self = shift;
   my ($hdr) = @_;
   $hdr->{tv_sec}.'.'.sprintf("%06d", $hdr->{tv_usec});
}

sub _setTimestamp {
   my $self = shift;
   my @time = Time::HiRes::gettimeofday();
   $time[0].'.'.sprintf("%06d", $time[1]);
}

my $mapLinks = {
   NP_DUMP_LINK_NULL()   => NP_LAYER_NULL(),
   NP_DUMP_LINK_EN10MB() => NP_LAYER_ETH(),
   NP_DUMP_LINK_RAW()    => NP_LAYER_RAW(),
   NP_DUMP_LINK_SLL()    => NP_LAYER_SLL(),
   NP_DUMP_LINK_PPP()    => NP_LAYER_PPP(),
};

sub _pcapNext {
   my $self = shift;

   my %hdr;
   if (my $raw = Net::Pcap::next($self->[$___pcapd], \%hdr)) {
      my $ts = $self->[$__keepTimestamp] ? $self->_getTimestamp(\%hdr)
                                         : $self->_setTimestamp;
      my $frame = Net::Packet::Frame->new(
         env         => $self->env,
         raw         => $raw,
         timestamp   => $ts,
         encapsulate => $mapLinks->{$self->[$__link]} || NP_LAYER_UNKNOWN,
      ) or return undef;

      $self->_addToFramesSorted($frame) unless $self->[$__noStore];
      return $frame;
   }

   undef;
}

sub _getNextAwaitingFrameOffline {
   my $self = shift;
   $self->_pcapNext;
}

sub _getNextAwaitingFrameOnline {
   my $self = shift;
   my $last = $self->[$___sDataAwaiting];
   my $new  = $self->_sRetrieve;

   # Return if nothing new is awaiting
   return undef if ($new <= $last);

   $self->[$___sDataAwaiting]++;
   $self->_pcapNext;
}

sub _getNextAwaitingFrame {
   my $self = shift;
   $self->isModeOnline ? $self->_getNextAwaitingFrameOnline
                       : $self->_getNextAwaitingFrameOffline;
}

# XXX: need more work
#sub _getNextAwaitingFrames {
   #my $self = shift;
   #my $last = $self->[$___sDataAwaiting];
   #my $new  = $self->_sRetrieve;
   #my $diff = $new - $last;
   #return [] if $diff <= 0; # Nothing awaiting
   #$self->[$___sDataAwaiting] += $diff;
   #my $frames = [];
   #while ($diff--) {
      #push @$frames, $self->_pcapNext;
   #}
   #$frames;
#}

sub _nextTimeoutHandle {
   my $self = shift;

   # Handle timeout
   my $thisTime = gettimeofday()      if     $self->[$__timeoutOnNext];
   $self->[$___firstTime] = $thisTime unless $self->[$___firstTime];

   if ($self->[$__timeoutOnNext] && $self->[$___firstTime]) {
      if (($thisTime - $self->[$___firstTime]) > $self->[$__timeoutOnNext]) {
         $self->[$__timeout]    = 1;
         $self->[$___firstTime] = 0;
         $self->cgDebugPrint(1, "Timeout occured");
         return undef;
      }
   }
   1;
}

sub _nextTimeoutReset { shift->[$___firstTime] = 0 }

sub next {
   my $self = shift;

   unless ($self->[$__isRunning]) {
      croak("You MUST call start() method before using next() method\n");
   }

   $self->_nextTimeoutHandle or return undef;

   my $frame = $self->_getNextAwaitingFrame;
   $self->_nextTimeoutReset if $frame;

   $frame ? do { $self->[$__nextFrame] = $frame } : undef;
}

sub nextAll { my $self = shift; while ($self->next) {} }

sub write {
   my $self = shift;
   my ($frame) = @_;

   unless ($self->isModeWriter) {
      croak("Dump is not in writer mode\n");
   }

   # Rebuild the frame without possible layer 2
   my $new;
   $new .= $frame->l3->raw if $frame->l3;
   $new .= $frame->l4->raw if $frame->l4;
   $new .= $frame->l7->raw if $frame->l7;

   # Create pcap header
   my ($sec, $usec) = split('\.', $frame->timestamp);
   my $hdr = {
      len     => length($new),
      caplen  => length($new),
      tv_sec  => $sec,
      tv_usec => $usec,
   };

   Net::Pcap::pcap_dump($self->[$___dumper], $hdr, $new);
   Net::Pcap::dump_flush($self->[$___dumper]);
}

# XXX: broken for now
#sub nextAll {
   #my $self = shift;
   #$self->_nextTimeoutHandle or return [];

   #my $frames = $self->_getNextAwaitingFrames;
   #$self->_nextTimeoutReset if @$frames;
   #$frames;
#}

sub timeoutReset { shift->[$__timeout] = 0 }

sub framesFor {
   my $self = shift;
   my ($f) = @_;

   my $l2Key = ($f->l2 && $f->l2->getKeyReverse($f)) || 'all';
   my $l3Key = ($f->l3 && $f->l3->getKeyReverse($f)) || 'all';
   my $l4Key = ($f->l4 && $f->l4->getKeyReverse($f)) || 'all';
   my $aref = $self->[$__framesSorted]->{$l2Key}{$l3Key}{$l4Key};

   $aref ? @$aref : ();
}

#
# Other accessors
#

sub framesSorted {
   my $self = shift;
   my ($f) = @_;

   if ($f) {
      # Wipe headers, since if not, framesFor() will not be able to find them.
      # Because if you create a Frame from L3, no headers are set for L2, but 
      # the Dump will have them and store them into the l2Key.
      if ($self->env->desc && ! $self->[$__noLayerWipe]) {
         $f->l2(undef) if ref($self->env->desc) =~ /L3|L4/;
         $f->l3(undef) if ref($self->env->desc) =~ /L4/;
      }

      my $l2Key = ($f->l2 && $f->l2->getKey($f)) || 'all';
      my $l3Key = ($f->l3 && $f->l3->getKey($f)) || 'all';
      my $l4Key = ($f->l4 && $f->l4->getKey($f)) || 'all';
      push @{$self->[$__framesSorted]->{$l2Key}{$l3Key}{$l4Key}}, $f;

      # We store a second time for ICMP messages
      if ($f->isIcmp) {
         my $l3Key = ($f->l3 && $f->l3->is.':'.$f->l3->dst) || 'all';
         push @{$self->[$__framesSorted]->{$l2Key}{$l3Key}{$l4Key}}, $f;
      }
   }

   $self->[$__framesSorted];
}

1;

__END__

=head1 NAME

Net::Packet::Dump - a tcpdump-like object providing frame capturing and more

=head1 SYNOPSIS

   require Net::Packet::Dump;
   use Net::Packet::Consts qw(:dump);

   #
   # Example live capture (sniffer like)
   #

   # Instanciate object
   my $dump = Net::Packet::Dump->new(
      mode          => NP_DUMP_MODE_ONLINE,
      file          => 'live.pcap',
      filter        => 'tcp',
      promisc       => 1,
      noStore       => 1,
      keepTimestamp => 1,
      unlinkOnClean => 0,
      overwrite     => 1,
   );
   # Start capture
   $dump->start;

   while (1) {
      if (my $frame = $dump->next) {
         print $frame->l2->print, "\n" if $frame->l2;
         print $frame->l3->print, "\n" if $frame->l3;
         print $frame->l4->print, "\n" if $frame->l4;
         print $frame->l7->print, "\n" if $frame->l7;
      }
   }

   # Cleanup
   $dump->stop;
   $dump->clean;

   #
   # Example offline analysis
   #

   my $dump2 = Net::Packet::Dump->new(
      mode          => NP_DUMP_MODE_OFFLINE,
      file          => 'existant-file.pcap',
      unlinkOnClean => 0,
   );

   # Analyze the .pcap file, build an array of Net::Packet::Frame's
   $dump2->start;
   $dump2->nextAll;

   # Browses captured frames
   for ($dump2->frames) {
      # Do what you want
      print $_->l2->print, "\n" if $_->l2;
      print $_->l3->print, "\n" if $_->l3;
      print $_->l4->print, "\n" if $_->l4;
      print $_->l7->print, "\n" if $_->l7;
   }

   # Cleanup
   $dump2->stop;
   $dump2->clean;

   #
   # Example writing mode
   #

   my $dump3 = Net::Packet::Dump->new(
      mode      => NP_DUMP_MODE_WRITER,
      file      => 'write.pcap',
      overwrite => 1,
   );

   $dump3->start;

   # Build or capture some frames here
   my $frame = Net::Packet::Frame->new;

   # Write them
   $dump3->write($frame);

   # Cleanup
   $dump3->stop;
   $dump3->clean;

=head1 DESCRIPTION

This module is the capturing part of Net::Packet framework. It is basically a tcpdump process. When a capture starts, the tcpdump process is forked, and saves all traffic to a .pcap file. The parent process can call B<next> or B<nextAll> to convert captured frames from .pcap file to B<Net::Packet::Frame>s.

Then, you can call B<recv> method on your sent frames to see if a corresponding reply is waiting in the B<frames> array attribute of B<Net::Packet::Dump>.

By default, if you use this module to analyze frames you've sent (very likely ;)), and you've sent those frames at layer 4 (using B<Net::Packet::DescL4>) (for example), lower layers will be wiped on storing in B<frames> array. This behaviour can be disabled by using B<noLayerWipe> attribute.

Since B<Net::Packet> 3.00, it is also possible to create complete .pcap files, thanks to the writer mode (see B<SYNOPSIS>).

=head1 ATTRIBUTES

=over 4

=item B<dev>

By default, this attribute is set to B<dev> found in default B<$Env> object. You can overwrite it by specifying another one in B<new> constructor.

=item B<env>

Stores a B<Net::Packet::Env> object. It is used in B<start> method, for example. The default is to use the global B<$Env> object created when using B<Net::Packet::Env>.

=item B<file>

Where to save captured frames. By default, a random name file is chosen, named like `netpacket-tmp-$$.@{[getRandom32bitsInt()]}.pcap'.

=item B<filter>

A pcap filter to restrain what to capture. It also works in offline mode, to analyze only what you want, and not all traffic. Default to capture all traffic. WARNING: every time a packet passes this filter, and the B<next> method is called, the internal counter used by b<timeoutOnNext> is reset. So the B<timeout> attribute can only be used if you know exactly that the filter will only catch what you want and not perturbating traffic.

=item B<overwrite>

If the B<file> exists, setting this to 1 will overwrite it. Default to not overwrite it.

=item B<timeoutOnNext>

Each time B<next> method is called, an internal counter is incremented if no frame has been captured. When a frame is captured (that is, a frame passed the pcap filter), the B<timeout> attribute is reset to 0. When the counter reaches the value of B<timeoutOnNext>, the B<timeout> attribute is set to 1, meaning no frames have been captured during the specified amount of time. Default to 3 seconds.

=item B<timeout>

Is auto set to 1 when a timeout has occured. It is not reset to 0 automatically, you need to do it yourself.

=item B<promisc>

If you want to capture in promiscuous mode, set it to 1. Default to 0.

=item B<link>

This attribute tells which datalink type is used for .pcap files.

=item B<nextFrame>

This one stores a pointer to the latest received frame after a call to B<next> method. If a B<next> call is done, and no frame is received, this attribute is set to undef.

=item B<isRunning>

When the capturing process is running (B<start> has been called), this is set to 1. So, when B<start> method has been called, it is set to 1, and when B<stop> method is called, set to 0.

=item B<unlinkOnClean>

When the B<clean> method is called, and this attribute is set to 1, the B<file> is deleted from disk. Set it to 0 to avoid this behaviour. BEWARE: default to 1.

=item B<noStore>

If you set this attribute to 1, frames will not be stored in B<frames> array. It is used in sniffer-like programs, in order to avoid memory exhaustion by keeping all captured B<Net::Packet::Frame> into memory. Default is to store frames.

=item B<noLayerWipe>

As explained in DESCRIPTION, if you send packets at layer 4, layer 2 and 3 are not keeped when stored in B<frames>. The same is true when sending at layer 3 (layer 2 is not kept). Default to wipe those layers. WARNING: if you set it to 1, and you need the B<recv> method from B<Net::Packet::Frame>, it will fail. In fact, this is a speed improvements, that is in order to find matching frame for your request, they are stored in a hash, using layer as keys (B<getKey> and B<getKeyReverse> are used to get keys from each layer. So, if you do not wipe layers, a key will be used to store the frame, but another will be used to search for it, and no match will be found. This is a current limitation I'm working on to remove.

=item B<mode>

When you crate a B<Net::Packet::Dump>, you have 3 possible modes : online, offline and writer. You need to load constants from B<Net::Packet::Consts> to have access to that (see B<SYNOPSIS>). The three constants are:

NP_DUMP_MODE_ONLINE

NP_DUMP_MODE_OFFLINE

NP_DUMP_MODE_WRITER

Default behaviour is to use online mode.

=item B<keepTimestamp>

Sometimes, when frames are captured and saved to a .pcap file, timestamps sucks. That is, you send a frame, and receive the reply, but your request appear to have been sent after the reply. So, to correct that, you can use B<Net::Packet> framework own timestamping system. The default is 0. Set it manually to 1 if you need original .pcap frames timestamps.

=item B<frames> [is an arrayref]

Stores all analyzed frames found in a pcap file in this arrayref.

=item B<framesSorted> [is an hashref]

Stores all analyzed frames found in a pcap file in this hashref, using keys to store and search related frames request/replies.

=back

=head1 METHODS

=over 4

=item B<new>

Object contructor. Default values for attributes:

dev:             $Env->dev

env:             $Env

file:            "netpacket-tmp-$$.@{[getRandom32bitsInt()]}.pcap"

filter:          ''

overwrite:       0

timeout:         0

promisc:         0

timeoutOnNext:   3

isRunning:       0

unlinkOnClean:   1

noStore:         0

noLayerWipe:     0

mode:            NP_DUMP_MODE_ONLINE

keepTimestamp:   0

=item B<isModeOnline>

=item B<isModeOffline>

=item B<isModeWriter>

Returns 1 if B<Net::Packet::Dump> object is respectively set to online, offline or writer mode. 0 otherwise.

=item B<start>

You MUST manually call this method to start frame capture, whatever mode you are in. In online mode, it will fork a tcpdump-like process to save captured frames to a .pcap file. It will not overwrite an existing file by default, use B<overwrite> attribute for that. In offline mode, it will only provide analyzing methods. In writer mode, it will only provide writing methods for frames. It will set B<isRunning> attribute to 1 when called.

=item B<stop>

You MUST manually call this method to stop the process. In online mode, it will not remove the generated .pcap file, you MUST call B<clean> method. In offline mode, it will to nothing. In writer mode, it will call B<Net::Pcap::dump_close> method. Then, it will set B<isRunning> attribute to 0.

=item B<isFather>

=item B<isSon>

These methods will tell you if your current process is respectively the father, or son process of B<Net::Packet::Dump> object.

=item B<clean>

You MUST call this method manually. It will never be called by B<Net::Packet> framework. This method will remove the generated .pcap file in online mode if the B<unlinkOnClean> attribute is set to 1. In other modes, it will do nothing.

=item B<getStats>

Tries to get packet statistics on an open descriptor. It returns a reference to a hash that has to following fields: B<ps_recv>, B<ps_drop>, B<ps_ifdrop>.

=item B<flush>

Will removed all analyzed frames from B<frames> array and B<framesSorted> hash. Use it with caution, because B<recv> from B<Net::Packet::Frame> relies on those.

=item B<next>

Returns the next captured frame; undef if none found in .pcap file. In all cases, B<nextFrame> attribute is set (either to the captured frame or undef). Each time this method is run, a comparison is done to see if no frame has been captured during B<timeoutOnNext> amount of seconds. If so, B<timeout> attribute is set to 1 to reflect the pending timeout. When a frame is received, it is stored in B<frames> arrayref, and in B<framesSorted> hashref, used to quickly B<recv> it (see B<Net::Packet::Frame>), and internal counter for time elapsed since last received packet is reset.

=item B<nextAll>

Calls B<next> method until it returns undef (meaning no new frame waiting to be analyzed from pcap file).

=item B<write> (scalar)

In writer mode, this method takes a B<Net::Packet::Frame> as a parameter, and writes it to the .pcap file. Works only in writer mode.

=item B<timeoutReset>

Used to reset manually the B<timeout> attribute. This is a helper method.

=item B<framesFor> (scalar)

You pass a B<Net::Packet::Frame> has parameter, and it returns an array of all frames relating to the connection. For example, when you send a TCP SYN packet, this method will return TCP packets relating to the used source/destination IP, source/destination port, and also related ICMP packets.

=item B<framesSorted> (scalar)

Method mostly used internally to store in a hashref a captured frame. This is used to retrieve it quickly on B<recv> call.

=back

=head1 CONSTANTS

=over 4

=item B<NP_DUMP_LINK_NULL>

=item B<NP_DUMP_LINK_EN10MB>

=item B<NP_DUMP_LINK_RAW>

=item B<NP_DUMP_LINK_SLL>

Constants for first layers within the pcap file.

=item B<NP_DUMP_MODE_OFFLINE>

=item B<NP_DUMP_MODE_ONLINE>

=item B<NP_DUMP_MODE_WRITER>

Constants to set the dump mode.

=back

=head1 AUTHOR

Patrice E<lt>GomoRE<gt> Auffret

=head1 COPYRIGHT AND LICENSE

Copyright (c) 2004-2006, Patrice E<lt>GomoRE<gt> Auffret

You may distribute this module under the terms of the Artistic license.
See LICENSE.Artistic file in the source distribution archive.

=head1 RELATED MODULES

L<NetPacket>, L<Net::RawIP>, L<Net::RawSock>

=cut
