File Coverage

blib/lib/Net/Pcap/Easy.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1              
2             package Net::Pcap::Easy;
3              
4 15     15   10300 use strict;
  15         19  
  15         465  
5 15     15   56 no warnings;
  15         17  
  15         431  
6              
7 15     15   70 use Carp;
  15         19  
  15         917  
8 15     15   8550 use Socket;
  15         47251  
  15         6647  
9 15     15   13057 use Net::Pcap;
  0            
  0            
10             use Net::Netmask;
11             use NetPacket::Ethernet qw(:types);
12             use NetPacket::IP qw(:protos);
13             use NetPacket::ARP qw(:opcodes);
14             use NetPacket::TCP;
15             use NetPacket::UDP;
16             use NetPacket::IGMP;
17             use NetPacket::ICMP qw(:types);
18              
19             our $VERSION = "1.4210";
20             our $MIN_SNAPLEN = 256;
21             our $DEFAULT_PPL = 32;
22              
23             my %KNOWN_CALLBACKS = (map {($_=>1)} qw(
24             appletalk_callback arp_callback arpreply_callback arpreq_callback default_callback icmp_callback
25             icmpechoreply_callback icmpunreach_callback icmpsourcequench_callback icmpredirect_callback
26             icmpecho_callback icmprouteradvert_callback icmproutersolicit_callback icmptimxceed_callback
27             icmpparamprob_callback icmptstamp_callback icmptstampreply_callback icmpireq_callback
28             icmpireqreply_callback igmp_callback ipv4_callback ipv6_callback ppp_callback rarpreply_callback
29             rarpreq_callback snmp_callback tcp_callback udp_callback
30             ));
31              
32             sub DESTROY {
33             my $this = shift;
34             $this->close;
35             %$this = ();
36             return;
37             }
38              
39             sub close {
40             my $this = shift;
41              
42             my $p = delete $this->{pcap};
43             Net::Pcap::close($p) if $p;
44              
45             return;
46             }
47              
48             sub is_local {
49             my $this = shift;
50             my $nm = $this->cidr;
51              
52             my $r = eval { $nm->contains( @_ ) }; croak $@ if $@;
53             return $r;
54             }
55              
56             sub new {
57             my $class = shift;
58             my $this = bless { @_ }, $class;
59              
60             my $err;
61             my $pcap;
62             unless ($this->{pcap}) {
63             my $dev = $this->{dev};
64              
65             if( $dev =~ s/^file:// ) {
66             $pcap = $this->{pcap} =
67             Net::Pcap::open_offline($dev, \$err)
68             or die "error opening offline pcap file: $err";
69              
70             } else {
71             unless( $dev ) {
72             $dev = $this->{dev} = Net::Pcap::lookupdev(\$err);
73             croak "ERROR while trying to find a device: $err" unless $dev;
74             }
75              
76             my ($network, $netmask);
77             if (Net::Pcap::lookupnet($dev, \$network, \$netmask, \$err)) {
78             croak "ERROR finding net and netmask for $dev: $err";
79              
80             } else {
81             $this->{network} = $network;
82             $this->{netmask} = $netmask;
83             }
84              
85             my $ppl = $this->{packets_per_loop};
86             $ppl = $this->{packets_per_loop} = $DEFAULT_PPL unless defined $ppl and $ppl > 0;
87              
88             my $ttl = $this->{timeout_in_ms} || 250;
89             $ttl = 250 if $ttl < 0;
90              
91             my $snaplen = $this->{bytes_to_capture} || 1024;
92             $snaplen = $MIN_SNAPLEN unless $snaplen >= 256;
93              
94             $pcap = $this->{pcap} = Net::Pcap::open_live($dev, $snaplen, $this->{promiscuous}, $ttl, \$err);
95              
96             croak "ERROR opening pacp session: $err" if $err or not $pcap;
97             }
98              
99             for my $f (grep {m/_callback$/} keys %$this) {
100             croak "the $f option does not point to a CODE ref" unless ref($this->{$f}) eq "CODE";
101             warn "the $f option is not a known callback and will never get called" unless $KNOWN_CALLBACKS{$f};
102             }
103             }
104              
105             if( my $f = $this->{filter} ) {
106             my $filter;
107             Net::Pcap::compile( $pcap, \$filter, $f, 1, $this->{netmask} ) && croak 'ERROR compiling pcap filter';
108             Net::Pcap::setfilter( $pcap, $filter ) && die 'ERROR Applying pcap filter';
109             }
110              
111             return $this;
112             }
113              
114             sub _main_callback {
115             my ($this, $linktype, $header, $packet) = @_;
116              
117             # For non-ethernet data link types, construct a
118             # fake ethernet header from the data available.
119             my ($ether, $type);
120             if ($linktype == Net::Pcap::DLT_EN10MB) {
121             $ether = NetPacket::Ethernet->decode($packet);
122             $type = $ether->{type};
123              
124             } elsif ($linktype == Net::Pcap::DLT_LINUX_SLL) {
125             use bytes;
126             $type = unpack("n", substr($packet, 2+2+2+8, 2));
127             $ether = NetPacket::Ethernet->decode(
128             pack("h24 n", "0" x 24, $type) . substr($packet, 16));
129             no bytes;
130              
131             } else {
132             die "ERROR Unhandled data link type: " .
133             Net::Pcap::datalink_val_to_name($linktype);
134             }
135              
136             $this->{_pp} ++;
137              
138             my $cb;
139              
140             return $this->_ipv4( $ether, NetPacket::IP -> decode($ether->{data}), $header) if $type == ETH_TYPE_IP;
141             return $this->_arp( $ether, NetPacket::ARP -> decode($ether->{data}), $header) if $type == ETH_TYPE_ARP;
142            
143             return $cb->($this, $ether, $header) if $type == ETH_TYPE_IPv6 and $cb = $this->{ipv6_callback};
144             return $cb->($this, $ether, $header) if $type == ETH_TYPE_SNMP and $cb = $this->{snmp_callback};
145             return $cb->($this, $ether, $header) if $type == ETH_TYPE_PPP and $cb = $this->{ppp_callback};
146             return $cb->($this, $ether, $header) if $type == ETH_TYPE_APPLETALK and $cb = $this->{appletalk_callback};
147              
148             return $cb->($this, $ether, $header) if $cb = $this->{default_callback};
149             }
150              
151             sub _icmp {
152             my ($this, $ether, $ip, $icmp, $header) = @_;
153              
154             my $cb;
155             my $type = $icmp->{type};
156              
157             return $cb->($this, $ether, $ip, $icmp, $header ) if $type == ICMP_ECHOREPLY and $cb = $this->{icmpechoreply_callback};
158             return $cb->($this, $ether, $ip, $icmp, $header ) if $type == ICMP_UNREACH and $cb = $this->{icmpunreach_callback};
159             return $cb->($this, $ether, $ip, $icmp, $header ) if $type == ICMP_SOURCEQUENCH and $cb = $this->{icmpsourcequench_callback};
160             return $cb->($this, $ether, $ip, $icmp, $header ) if $type == ICMP_REDIRECT and $cb = $this->{icmpredirect_callback};
161             return $cb->($this, $ether, $ip, $icmp, $header ) if $type == ICMP_ECHO and $cb = $this->{icmpecho_callback};
162             return $cb->($this, $ether, $ip, $icmp, $header ) if $type == ICMP_ROUTERADVERT and $cb = $this->{icmprouteradvert_callback};
163             return $cb->($this, $ether, $ip, $icmp, $header ) if $type == ICMP_ROUTERSOLICIT and $cb = $this->{icmproutersolicit_callback};
164             return $cb->($this, $ether, $ip, $icmp, $header ) if $type == ICMP_TIMXCEED and $cb = $this->{icmptimxceed_callback};
165             return $cb->($this, $ether, $ip, $icmp, $header ) if $type == ICMP_PARAMPROB and $cb = $this->{icmpparamprob_callback};
166             return $cb->($this, $ether, $ip, $icmp, $header ) if $type == ICMP_TSTAMP and $cb = $this->{icmptstamp_callback};
167             return $cb->($this, $ether, $ip, $icmp, $header ) if $type == ICMP_TSTAMPREPLY and $cb = $this->{icmptstampreply_callback};
168             return $cb->($this, $ether, $ip, $icmp, $header ) if $type == ICMP_IREQ and $cb = $this->{icmpireq_callback};
169             return $cb->($this, $ether, $ip, $icmp, $header ) if $type == ICMP_IREQREPLY and $cb = $this->{icmpireqreply_callback};
170              
171             # NOTE: MASKREQ is exported as MASREQ ... grrz: http://rt.cpan.org/Ticket/Display.html?id=37931
172             return $cb->($this, $ether, $ip, $icmp, $header ) if $type == NetPacket::ICMP::ICMP_MASKREQ() and $cb = $this->{icmpmaskreq_callback};
173             return $cb->($this, $ether, $ip, $icmp, $header ) if $type == ICMP_MASKREPLY and $cb = $this->{icmpmaskreply_callback};
174              
175             return $cb->($this, $ether, $ip, $icmp, $header ) if $cb = $this->{icmp_callback};
176             return $cb->($this, $ether, $ip, $icmp, $header ) if $cb = $this->{ipv4_callback};
177             return $cb->($this, $ether, $ip, $icmp, $header ) if $cb = $this->{default_callback};
178              
179             return;
180             }
181              
182             sub _ipv4 {
183             my ($this, $ether, $ip, $header) = @_;
184              
185             my $cb;
186             my $proto = $ip->{proto};
187              
188             # NOTE: this could probably be made slightly more efficient and less repeatative.
189              
190             return $cb->($this, $ether, $ip, NetPacket::TCP -> decode($ip->{data}), $header) if $proto == IP_PROTO_TCP and $cb = $this->{tcp_callback};
191             return $cb->($this, $ether, $ip, NetPacket::UDP -> decode($ip->{data}), $header) if $proto == IP_PROTO_UDP and $cb = $this->{udp_callback};
192             return $this->_icmp($ether,$ip, NetPacket::ICMP -> decode($ip->{data}), $header) if $proto == IP_PROTO_ICMP;
193             return $cb->($this, $ether, $ip, NetPacket::IGMP -> decode($ip->{data}), $header) if $proto == IP_PROTO_IGMP and $cb = $this->{igmp_callback};
194              
195             my $spo;
196             $spo = NetPacket::TCP -> decode($ip->{data}) if $proto == IP_PROTO_TCP;
197             $spo = NetPacket::UDP -> decode($ip->{data}) if $proto == IP_PROTO_UDP;
198             $spo = NetPacket::IGMP -> decode($ip->{data}) if $proto == IP_PROTO_IGMP;
199              
200             return $cb->($this, $ether, $ip, $spo, $header) if $cb = $this->{ipv4_callback};
201             return $cb->($this, $ether, $ip, $spo, $header) if $cb = $this->{default_callback};
202              
203             return;
204             }
205              
206             sub _arp {
207             my ($this, $ether, $arp, $header) = @_;
208              
209             my $cb;
210             my $op = $arp->{opcode};
211              
212             return $cb->($this, $ether, $arp, $header) if $op == ARP_OPCODE_REQUEST and $cb = $this->{arpreq_callback};
213             return $cb->($this, $ether, $arp, $header) if $op == ARP_OPCODE_REPLY and $cb = $this->{arpreply_callback};
214             return $cb->($this, $ether, $arp, $header) if $op == RARP_OPCODE_REQUEST and $cb = $this->{rarpreq_callback};
215             return $cb->($this, $ether, $arp, $header) if $op == RARP_OPCODE_REPLY and $cb = $this->{rarpreply_callback};
216              
217             return $cb->($this, $ether, $arp, $header) if $cb = $this->{arp_callback};
218             return $cb->($this, $ether, $arp, $header) if $cb = $this->{default_callback};
219              
220             return;
221             }
222              
223             sub loop {
224             my $this = shift;
225             my $cb = shift || sub { _main_callback($this, @_) };
226              
227             return unless exists $this->{pcap}; # in case we close early
228              
229             my $ret = Net::Pcap::loop($this->{pcap}, $this->{packets_per_loop}, $cb, Net::Pcap::datalink($this->{pcap}));
230              
231             return unless $ret == 0;
232             return (delete $this->{_pp}) || 0; # return the number of processed packets.
233             }
234              
235             sub pcap { return $_[0]->{pcap} }
236             sub raw_network { return $_[0]->{network} }
237             sub raw_netmask { return $_[0]->{netmask} }
238             sub dev { return $_[0]->{dev} }
239              
240             sub network {
241             my $this = shift;
242              
243             return Socket::inet_ntoa(scalar reverse pack("l", $this->{network}));
244             }
245              
246             sub netmask {
247             my $this = shift;
248              
249             return Socket::inet_ntoa(scalar reverse pack("l", $this->{netmask}));
250             }
251              
252             sub cidr {
253             my $this = shift;
254             my $nm = $this->{nm};
255             $nm = $this->{nm} = Net::Netmask->new($this->network . "/" . $this->netmask) unless $this->{nm};
256              
257             return $nm;
258             }
259              
260             sub stats {
261             my $this = shift;
262              
263             return unless exists $this->{pcap}; # in case we close early
264              
265             my %stats;
266             Net::Pcap::pcap_stats($this->{pcap}, \%stats);
267             $stats{ substr $_, 3 } = delete $stats{$_} for keys %stats;
268              
269             return wantarray ? %stats : \%stats;
270             }
271              
272             1;