package Net::Analysis::EventLoop;
# $Id: EventLoop.pm 131 2005-10-02 17:24:31Z abworrall $
use 5.008000;
our $VERSION = '0.01';
use strict;
use warnings;
use Carp qw(carp croak confess);
use NetPacket::Ethernet qw(:ALL);
use NetPacket::IP qw(:ALL);
use NetPacket::TCP qw(:ALL);
use NetPacket::UDP qw(:ALL);
use Net::Pcap;
use Params::Validate qw(:all);
use Net::Analysis::Packet qw(:all);
#### Public methods
#
# {{{ new
sub new {
my ($class) = shift;
my ($self) = bless ({pkt_number => 0}, $class);
my %h = validate (@_, {dispatcher => { can => 'emit_event' }});
$self->{dispatcher} = $h{dispatcher};
return $self;
}
# }}}
# {{{ loop_file
sub loop_file {
my ($self) = shift;
my %h = validate (@_, { filename => { type => SCALAR } });
my ($np_err);
my ($pcap_t) = Net::Pcap::open_offline ($h{filename}, \$np_err);
carp "event_loop('$h{filename}') failed: '$np_err'\n" if (defined $np_err);
$self->_event_loop ($pcap_t);
}
# }}}
# {{{ loop_net
sub loop_net {
my ($self) = shift;
my %h = validate (@_, { filter => { type => SCALAR } });
# See 'man Net::Pcap' for more details on these settings.
my $promiscuity = 0;
my $snaplen = 10240; # Must be >1540, else we will miss bytes
my $timeout_ms = 0;
my $optimize_filter = 1;
my ($np_err, $net, $mask, $filter_t);
my $dev = Net::Pcap::lookupdev(\$np_err);
Net::Pcap::lookupnet ($dev, \$net, \$mask, \$np_err);
my $pcap_t = Net::Pcap::open_live($dev, $snaplen, $promiscuity,
$timeout_ms, \$np_err);
if (defined $np_err) {
carp "loop_net(filter=>'$h{filter}') failed: '$np_err'\n";
}
if (Net::Pcap::compile ($pcap_t, \$filter_t, $h{filter},
$optimize_filter, $net) == -1)
{
carp "unable to compile filter string '$h{filter}'\n";
}
Net::Pcap::setfilter ($pcap_t, $filter_t);
$self->_event_loop ($pcap_t);
}
# }}}
# {{{ summary
sub summary {
my ($self) = @_;
print "---{ parse summary }---\n";
foreach (sort {$self->{n_pkts}{$b} <=> $self->{n_pkts}{$a}} keys %{$self->{n_pkts}})
{
printf " %-40.40s: % 7d\n", $_, $self->{n_pkts}{$_};
}
}
# }}}
#### Private helper methods
#
# {{{ _netpacket_packet_to_our_packet
sub _netpacket_packet_to_our_packet {
my ($self, $wire_pkt, $wire_hdrs) = @_;
# We assume ethernet capture ...
my ($eth_obj) = NetPacket::Ethernet->decode ($wire_pkt);
# A flexible OO dispatch scheme is probably where this is heading ...
if ($eth_obj->{type} == ETH_TYPE_IP) {
my $ip_obj = NetPacket::IP->decode($eth_obj->{data});
if($ip_obj->{proto} == IP_PROTO_TCP) {
# Some ethernet frames come with padding; this confuses NetPacket,
# so strip it off here before parsing the IP payload as a TCP
# packet.
my $ip_data_len = $ip_obj->{len} - $ip_obj->{hlen} * 4;
if ($ip_data_len < length($ip_obj->{data})) {
substr ($ip_obj->{data}, $ip_data_len) = '';
}
my $tcp_obj = NetPacket::TCP->decode ($ip_obj->{data});
#$self->{n_pkts}{"tcp_ok"}++;
# $ip_obj has the IP addresses
# $tcp_obj has the ports & TCP info, and the payload in {data}
# Create a 'vendor-neutral' packet, in case we leave NetPacket
my $pkt = ["$ip_obj->{dest_ip}:$tcp_obj->{dest_port}",
"$ip_obj->{src_ip}:$tcp_obj->{src_port}",
$tcp_obj->{flags},
$tcp_obj->{data},
$tcp_obj->{seqnum},
$tcp_obj->{acknum},
$self->{pkt_number}++,
# These are turned into the object $pkt->{time}
$wire_hdrs->{tv_sec},
$wire_hdrs->{tv_usec},
];
pkt_init($pkt);
return $pkt;
=pod
return Net::Analysis::Packet->new
({to => "$ip_obj->{dest_ip}:$tcp_obj->{dest_port}",
from => "$ip_obj->{src_ip}:$tcp_obj->{src_port}",
flags => $tcp_obj->{flags},
data => $tcp_obj->{data},
seqnum => $tcp_obj->{seqnum},
acknum => $tcp_obj->{acknum},
pkt_number => $self->{pkt_number}++,
# These are turned into the object $pkt->{time}
tv_sec => $wire_hdrs->{tv_sec},
tv_usec => $wire_hdrs->{tv_usec},
} );
=cut
#} elsif ($ip_obj->{proto} == IP_PROTO_UDP) {
# We should handle these at some point ...
#$self->{n_pkts}{"SKIP_ip_proto_UDP"}++;
#} else {
#$self->{n_pkts}{"SKIP_ip_proto_$ip_obj->{proto}"}++;
}
#} else {
# ARP ? AppleTalk ? SNMP ? IPv6 ? PPP ? Whatever, skip it
#$self->{n_pkts}{"SKIP_eth_pkt_type_$eth_obj->{type}"}++;
}
return undef;
}
# }}}
# {{{ _event_loop
sub _event_loop {
my ($self, $pcap_t) = @_;
$self->{dispatcher}->emit_event (name => 'setup');
while (1) {
my (%hdr);
my ($np_pkt) = Net::Pcap::next($pcap_t, \%hdr);
last if (!defined $np_pkt);
if ($hdr{len} != $hdr{caplen}) {
warn "incomplete packet - use tcpdump with option '-S 2048'\n";
next;
}
my $our_pkt = $self->_netpacket_packet_to_our_packet ($np_pkt, \%hdr);
next if (!defined $our_pkt);
# This will need re-jigging when we handle more than just TCP
$self->{dispatcher}->emit_event (name => 'tcp_packet',
args => {pkt => $our_pkt});
}
$self->{dispatcher}->emit_event (name => 'teardown');
}
# }}}
1;
__END__
# {{{ POD
=head1 NAME
Net::Analysis::EventLoop - generate a stream of packets
=head1 SYNOPSIS
use Net::Analysis::Dispatcher;
use Net::Analysis::EventLoop;
my ($d) = Net::Analysis::Dispatcher->new();
my ($el) = Net::Analysis::EventLoop->new (dispatcher => $d);
... register some listener modules onto the dispatcher ...
# Now run it over a file ...
$el->loop_file (filename => 'some.tpcdump');
# ... or try live capture (using the same filter syntax as tcpdump et al)
$el->loop_net (filter => 'port 80');
exit 0;
=head1 DESCRIPTION
This module provides the glue between the main dispatcher/listener stuff, and
the underlying source of packets.
It gets packets (currently via the NetPacket layer on top of Net::Pcap), turns
them into L<Net::Analysis::Packet>s, and then dispatches them to any listeners
who care about 'tcp_packets'.
Current limitations:
=over 4
=item *
Only TCP packets are handled
=item *
It's not designed to be fast; don't run on GB files unless you're about to go
home. Live capture on busy servers may not be the best either; make the most
specific filter you can, to allow Net::Pcap to reduce the number of packets
that get to here. And watch your memory; it's likely to leak.
=item *
Live capture is not perfectly integrated; there is no way to cleanly stop
capture at this time.
=back
=head2 EXPORT
None by default.
=head1 SEE ALSO
Net::Analysis::Dispatcher
=head1 AUTHOR
Adam B. Worrall, E<lt>worrall@cpan.orgE<gt>
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2004 by Adam B. Worrall
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.5 or,
at your option, any later version of Perl 5 you may have available.
=cut
# }}}
# {{{ -------------------------={ E N D }=----------------------------------
# Local variables:
# folded-file: t
# end:
# }}}
syntax highlighted by Code2HTML, v. 0.9.1