File Coverage

blib/lib/Net/Pcap/Reassemble.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Net::Pcap::Reassemble;
2              
3             require 5.002;
4 1     1   3331 use strict;
  1         4  
  1         89  
5 1     1   7 use warnings;
  1         2  
  1         52  
6 1     1   6 use vars qw($VERSION %pending $callback $linktype $debug $stripl2);
  1         7  
  1         131  
7              
8 1     1   5860 use Net::Pcap;
  0            
  0            
9             use Carp;
10              
11             #
12             # Copyright (c) 2006-2010 James Raftery . All rights reserved.
13             # This program is free software; you can redistribute it and/or
14             # modify it under the same terms as Perl itself.
15             # Please submit bug reports, patches and comments to the author.
16             #
17             # $Id: Reassemble.pm,v 1.22 2010/05/13 18:29:13 james Exp $
18             #
19             # This module is a wrapper for the loop() function of the Net::Pcap
20             # module. It performs IP fragment reassembly for fragmented datagrams
21             # in the libpcap dump data. You require the Net::Pcap module to use
22             # Net::Pcap::Reassemble. See the Net::Pcap::Reassemble(3) man page for
23             # more information.
24             #
25              
26             $VERSION = '0.05';
27             $debug = 0;
28             $stripl2 = 0;
29              
30             ####
31              
32             #
33             # Wrapper around Net::Pcap's loop() function. This takes the same
34             # arguments as Net::Pcap's loop().
35             #
36             sub loop ($$&$) {
37              
38             my ($pcap_t, $num, $user_data);
39              
40             ($pcap_t, $num, $callback, $user_data) = @_ or
41             croak('Missing arguments to loop()');
42              
43             defined($linktype = Net::Pcap::datalink($pcap_t)) or die;
44              
45             #
46             # A reference to the user's callback is in $callback, which is
47             # declared as a package global. We call Net::Pcap::loop,
48             # specifying instead our own _reassemble() sub as its callback.
49             # _reassemble() will give a packet to the sub referenced in
50             # $callback when it has a complete datagram.
51             #
52             return Net::Pcap::loop($pcap_t, $num, \&_reassemble, $user_data);
53             }
54              
55             sub flush () {
56             undef %pending;
57             }
58              
59             #
60             # Callback function. Read the IP version from the packet header and call
61             # the appropriate function to read it. If that function returns data
62             # (i.e. a complete datagram) then summon up the user's callback,
63             # supplying the packet.
64             #
65             sub _reassemble ($$$) {
66              
67             my ($user_data, $header, $packet, $ver, $l2);
68              
69             ($user_data, $header, $packet) = @_ or
70             croak('Missing arguments to _reassemble()');
71              
72             ($l2, $packet) = _splitpkt($packet);
73             $ver = unpack('C', $packet) >> 4;
74              
75             if ($ver == 4) {
76             $packet = _readIPv4pkt($packet);
77             } elsif ($ver == 6) {
78             $packet = _readIPv6pkt($packet);
79             } else {
80             $packet = undef;
81             }
82              
83             if ($packet) {
84             # Put back the layer 2 header data from the most recent packet
85             $packet = $l2.$packet unless $stripl2;
86             &$callback($user_data, $header, $packet);
87             }
88             }
89              
90             #
91             # Split the packet into layer 2 header and IP datagram (+ optional padding).
92             #
93             sub _splitpkt ($) {
94              
95             my ($packet, $bytes);
96              
97             $packet = shift or croak('Missing argument to _splitpkt()');
98              
99             if ($linktype == DLT_EN10MB) {
100             # ethernet header
101             $bytes = 14;
102             } elsif ($linktype == DLT_LOOP or $linktype == DLT_NULL) {
103             # loopback header
104             $bytes = 4;
105             } elsif ($linktype == DLT_RAW) {
106             # no header
107             $bytes = 0;
108             } elsif ($linktype == DLT_LINUX_SLL) {
109             # linux 'cooked'
110             $bytes = 16;
111             } else {
112             # barf
113             croak("unsupported linktype: $linktype");
114             }
115              
116             return unpack("a$bytes a*", $packet);
117             }
118              
119             #
120             # Read an IPv4 packet.
121             #
122             sub _readIPv4pkt ($) {
123              
124             my ($packet, $i, $ver, $ihl, $pktlen, $id, $mf, $offset, $proto,
125             $src, $dst, $payload, $datalen);
126              
127             $packet = shift or croak('Missing argument to _readIPv4pkt()');
128              
129             # The x's are: tos, ttl, chksum, options+data
130             ($i, $pktlen, $id, $offset, $proto, $src, $dst) =
131             unpack('C x n3 x C x2 a4 a4', $packet);
132              
133             $ver = $i >> 4;
134             $ihl = ($i & 0x0f) * 4;
135             $mf = ($offset >> 13) & 0x01; # More fragments flag
136             $offset = ($offset & 0x1fff) << 3;
137             $src = join('.', unpack('C*', $src));
138             $dst = join('.', unpack('C*', $dst));
139             $datalen = $pktlen - $ihl;
140              
141             print "ver:$ver ihl:$ihl packetlen:$pktlen id:$id mf:$mf " .
142             "offset:$offset datalen:$datalen proto:$proto\n".
143             "src:$src dst:$dst\n" if $debug;
144              
145             print "Dropping padding\n" if ($debug and length($packet) > $pktlen);
146             print "Incomplete packet\n" if (length($packet) < $pktlen);
147             $packet = substr($packet, 0, $pktlen);
148              
149             #
150             # Fragment 1: MF == 1, offset == 0
151             # Fragment 2..(n-1): MF == 1, offset > 0
152             # Fragment n: MF == 0, offset > 0
153             #
154              
155             #
156             # Can you encounter a negative offset? Maybe if we unpack the
157             # data incorrectly.
158             #
159             # If this isn't a fragment we drop down to the return statement
160             # which passes back the unmodified $packet data.
161             #
162             if (($mf and $offset >= 0) or ($offset > 0)) {
163             print "Fragment! ver:$ver ihl:$ihl packetlen:$pktlen id:$id ".
164             "mf:$mf offset:$offset datalen:$datalen proto:$proto\n".
165             "src:$src dst:$dst\n" if $debug;
166              
167             $i = "$src $dst $id $proto";
168              
169             #
170             # If initial fragment - use the whole packet as the data.
171             # XXX The user callback gets a packet with the header
172             # from the first fragment. 'total length' and MF
173             # are going to be wrong w.r.t. the reassembled
174             # packet.
175             #
176             if ($offset == 0) {
177             $payload = $packet;
178             } else {
179             $payload = substr($packet, $ihl, $datalen);
180             }
181              
182             #
183             # XXX We don't expunge old entries
184             #
185             if (exists $pending{$i}) {
186             $pending{$i}->addfragment($offset, $datalen, $mf,
187             $payload) or
188             print STDERR "addfragment: $offset $datalen $mf failed\n";
189             } else {
190             $pending{$i} = Net::Pcap::Reassemble::Packet->new($i,
191             $offset, $datalen, $mf, $payload) or
192             print STDERR "new Packet: $i $offset $datalen, $mf failed\n";
193             }
194              
195             print $pending{$i}->listfragments if $debug;
196              
197             # We get a packet if all the fragments have arrived, or
198             # an empty string if not.
199             $packet = $pending{$i}->iscomplete;
200             if ($packet) {
201             delete $pending{$i};
202             print "Fragment '$i' is complete.\n" if $debug;
203             }
204             }
205              
206             return $packet;
207             }
208              
209             #
210             # Read an IPv6 header/packet.
211             #
212             sub _readIPv6pkt ($) {
213              
214             my ($packet, $ver, $payloadlen, $nexthdr, $src, $dst, $payload, $i,
215             $offset, $id, $m, $hdrlen, $exthdrlentotal, $unfrag,
216             $unfragoffset, $prevhdr, $prevhdrlen);
217              
218             $packet = shift or croak('Missing argument to _readIPv6pkt()');
219             $prevhdr = 0; # Hackity, hack, hack
220              
221             # The x's are: class, label, hlim
222             ($ver, $payloadlen, $nexthdr, $src, $dst) =
223             unpack('C x3 n C x a16 a16', $packet);
224              
225             $ver >>= 4;
226             $src = join(':', unpack('H4'x8, $src));
227             $dst = join(':', unpack('H4'x8, $dst));
228             $exthdrlentotal = 0; # extension header bytes read so far
229              
230             print "ver:$ver payloadlen:$payloadlen nexthdr:$nexthdr\n" .
231             "src:$src\ndst:$dst\n" if $debug;
232              
233             # XXX not tested
234             print "Dropping padding\n" if ($debug and length($packet) > 40+$payloadlen);
235             print "Incomplete packet\n" if (length($packet) < 40+$payloadlen);
236             $packet = substr($packet, 0, 40+$payloadlen);
237             $payload = substr($packet, 40);
238              
239             #
240             # Since this module isn't a v6 capable end-host it doesn't
241             # implement TCP or UDP or any other `upper-layer' protocol. How
242             # do we decide when to stop looking ahead to the next header
243             # (and return some data to the caller)? We stop when we find
244             # a `next header' which isn't a known Extension Header:
245             #
246             # 0 Hop-by-Hop Options
247             # 43 Routing
248             # 44 Fragment
249             # 50 Encapsulating Security Payload
250             # 51 Authentication
251             # 60 Destination Options
252             #
253             # This means this will fail to deal with any subsequently added
254             # Extension Headers, which is sucky, but the alternative is to
255             # list all the other `next header' values and then break when a
256             # new one of them is defined :)
257             #
258             EXTHEADER: for (;;) {
259              
260             if ($nexthdr == 0 or $nexthdr == 43 or $nexthdr == 50 or
261             $nexthdr == 51 or $nexthdr == 60) {
262              
263             $prevhdr = $nexthdr;
264             $prevhdrlen = $hdrlen;
265             $exthdrlentotal += $hdrlen;
266             ($nexthdr, $hdrlen, $payload) = _readIPv6Extheader($payload);
267              
268             next EXTHEADER;
269             }
270              
271             last EXTHEADER if ($nexthdr != 44);
272              
273             #
274             # Fragment Header
275             #
276             ($nexthdr, $offset, $id, $m, $payload) = _readIPv6Fragheader($payload);
277              
278             $i = "$src $dst $id";
279              
280             #
281             # Initial fragment - use the whole packet minus the Fragment
282             # header as the data.
283             # Munge the Next Header value from 44 (Fragment Header) to that
284             # of the subsequent header.
285             #
286             # XXX The user callback gets a packet with the header from the
287             # first fragment. `length' is going to be wrong w.r.t. the
288             # reassembled packet.
289             #
290             if ($offset == 0) {
291              
292             # Offset to the start of the unfragmentable part
293             $unfragoffset = 40+$exthdrlentotal;
294             $unfrag = substr($packet, 0, $unfragoffset);
295              
296             if ($prevhdr == 0) {
297             # 6 bytes into IPv6 header
298             substr($unfrag, 6, 1) = $nexthdr;
299             } else {
300             # XXX not tested
301             # We've read N extension headers
302             # Wind back one header length ($prevhdrlen)
303             # from the start of the unfragmentable part
304             # ($unfragoffset).
305             substr($unfrag, $unfragoffset-$prevhdrlen, 1) = $nexthdr;
306             }
307              
308             $payload = $unfrag . $payload;
309             }
310              
311             #
312             # Fragmentable part length =
313             # packet payload length - length of extension headers read
314             # (add 8 bytes for the Fragment header)
315             #
316             $payloadlen -= ($exthdrlentotal+8);
317              
318             #
319             # XXX We don't expunge old entries
320             #
321             if (exists $pending{$i}) {
322             $pending{$i}->addfragment($offset, $payloadlen, $m,
323             $payload) or
324             print STDERR "addfrag: $i $offset $payloadlen $m failed\n";
325             } else {
326             $pending{$i} = Net::Pcap::Reassemble::Packet->new($i,
327             $offset, $payloadlen, $m, $payload) or
328             print STDERR "Packet: $i $offset $payloadlen $m failed\n";
329             }
330              
331             print $pending{$i}->listfragments if $debug;
332              
333             # We get a packet if all the fragments have arrived or an
334             # empty string if not.
335             $packet = $pending{$i}->iscomplete;
336             if ($packet) {
337             delete $pending{$i};
338             print "Fragment '$i' is complete.\n" if $debug;
339             }
340              
341             last EXTHEADER;
342              
343             } # End: EXTHEADER
344              
345             return $packet;
346             }
347              
348             #
349             # Read a standard IPv6 Extension Header. Extract the Next Header and
350             # Header Length values, and the payload.
351             #
352             sub _readIPv6Extheader ($) {
353              
354             my ($packet, $nexthdr, $hdrlen, $payload);
355              
356             $packet = shift or croak('Missing argument to _readIPv6Extheader()');
357              
358             ($nexthdr, $hdrlen) = unpack('CC', $packet);
359              
360             $hdrlen = $hdrlen*8 + 8;
361             print "Extension header is $hdrlen octets, nexthdr: $nexthdr\n" if $debug;
362              
363             # XXX not tested
364             # use substr?
365             $payload = unpack("x$hdrlen a*", $packet);
366              
367             return($nexthdr, $hdrlen, $payload);
368             }
369              
370             #
371             # Read an IPv6 Fragment Header. Extract the fragment's offset, ID, M
372             # flag and payload.
373             #
374             sub _readIPv6Fragheader ($) {
375              
376             my ($packet, $nexthdr, $offset, $m, $id, $payload);
377              
378             $packet = shift or croak('Missing argument to _readIPv6Fragheader()');
379              
380             ($nexthdr, $offset, $id, $payload) = unpack('C x n N a*', $packet);
381              
382             $m = $offset & 0x0001;
383             $offset >>= 3;
384             $offset *= 8;
385              
386             print "Fragment! header: nexthdr:$nexthdr offset:$offset ".
387             "id:$id,0x". unpack('H*', pack('N', $id)) ." m:$m ".
388             length($packet) . ' ' . length($payload) ."\n" if $debug;
389              
390             $nexthdr = pack('C', $nexthdr);
391             return ($nexthdr, $offset, $id, $m, $payload);
392             }
393              
394             ####
395              
396             package Net::Pcap::Reassemble::Packet;
397              
398             use strict;
399             use warnings;
400              
401             use Carp;
402              
403             #
404             # Constructor for a `Packet' object.
405             #
406             sub new ($$$$$$) {
407             my $proto = shift or croak;
408             my $class = ref($proto) || $proto;
409             defined(my $id = shift) or croak "No ID in $class constructor";
410             defined(my $offset = shift) or croak "No offset in $class constructor";
411             defined(my $length = shift) or croak "No length in $class constructor";
412             defined(my $mf = shift) or croak "No MF in $class constructor";
413             defined(my $data = shift) or croak "No data in $class constructor";
414              
415             #
416             # Each `Packet' object contains:
417             # 1. ID: IPv4: 'srcip dstip IPid protocol'
418             # IPv6: 'srcip dstip IPid'
419             # 2. A list of Net::Pcap::Reassemble::Fragment object references
420             # 3. The final octet, learned from the packet with MF==0.
421             # 4. A `sorted' flag to indicate if the fragment list is sorted
422             #
423             my $self = {
424             ID => $id,
425             FRAGS => [],
426             LASTOCTET => undef,
427             SORTED => 1,
428             };
429              
430             bless($self, $class);
431              
432             return undef if !$self->addfragment($offset, $length, $mf, $data);
433              
434             return $self;
435             }
436              
437             #
438             # Add a fragment to a Packet object.
439             #
440             sub addfragment ($$$$$) {
441             my $self = shift;
442             ref($self) or croak;
443              
444             my ($offset, $length, $mf, $data) = @_ or croak;
445              
446             my $frag =
447             Net::Pcap::Reassemble::Fragment->new($offset, $length, $mf, $data);
448             return undef if !$frag;
449              
450             # If this is the last fragment, save the last octet value in the
451             # object.
452             # XXX Check for more than one fragment with MF==0?
453             $self->{LASTOCTET} = $offset+$length if !$mf;
454              
455             # The list can't be considered sorted any more.
456             $self->{SORTED} = 0;
457              
458             # XXX Test for overlap?
459             return push(@{$self->{FRAGS}}, $frag);
460             }
461              
462             #
463             # Return a string showing the fragments that have been recieved by the object.
464             #
465             sub listfragments ($) {
466             my $self = shift;
467             ref($self) or croak;
468              
469             my ($s, $frag);
470              
471             $s .= "Packet ID:$self->{ID}\n";
472             $s .= "Last octet:$self->{LASTOCTET}\n" if (defined $self->{LASTOCTET});
473             foreach $frag (@{$self->{FRAGS}}) {
474             $s .= 'Fragment ' . $frag->vitals . "\n";
475             }
476              
477             return $s;
478             }
479              
480             #
481             # Check if all the fragments for a Packet have been received. If they have,
482             # splice the fragment data back together and return to the caller. If they
483             # have not, return no data.
484             #
485             sub iscomplete ($) {
486             my $self = shift;
487             ref($self) or croak;
488              
489             my $nextfrag = 0; # The first fragment starts at octet zero
490             my $data = '';
491             my $frag;
492              
493             #
494             # If we don't know LASTOCTET yet then we're missing at least the
495             # final (MF==0) fragment so we don't need to proceed any further.
496             #
497             return if (!defined $self->{LASTOCTET});
498              
499             #
500             # Sort the fragment list so we only need to scan it once.
501             # If it was unordered we would need to scan through it repeatedly.
502             # That said, sort() is pretty slow :)
503             #
504             FRAGMENT: foreach $frag (@{$self->_sortfragments}) {
505              
506             #
507             # If the first octet in this fragment is the octet we're
508             # searching for and the last octet is the last octet of the
509             # complete datagram then we have all the packet data. If not,
510             # the next fragment we search for is the one that starts where
511             # this one ends.
512             #
513             if ($frag->start == $nextfrag) {
514             last FRAGMENT if ($frag->end == $self->{LASTOCTET});
515             $nextfrag = $frag->end;
516             next FRAGMENT;
517             }
518              
519             #
520             # If we reach here, we're missing at least one fragment so
521             # just give up.
522             #
523             return;
524             }
525              
526             #
527             # The datagram is complete. Splice the fragments' data together
528             # to return the complete packet.
529             #
530             return $self->_data;
531             }
532              
533             #
534             # Return concatenated fragment data.
535             # Warning: missing fragments are blithely ignored. Use iscomplete() for
536             # a sanity-checked interface!
537             #
538             sub _data ($) {
539             my $self = shift;
540             ref($self) or croak;
541              
542             my ($frag, $data);
543              
544             foreach $frag (@{$self->_sortfragments}) {
545             $data .= $frag->data;
546             }
547              
548             return $data;
549             }
550              
551             #
552             # Sort the fragment list by starting octet value and return a reference
553             # the list.
554             #
555             sub _sortfragments ($) {
556             my $self = shift;
557             ref($self) or croak;
558              
559             if (!$self->{SORTED}) {
560             @{$self->{FRAGS}} = sort {$a->start<=>$b->start} @{$self->{FRAGS}};
561             $self->{SORTED} = 1;
562             }
563             return $self->{FRAGS};
564             }
565              
566             ####
567              
568             package Net::Pcap::Reassemble::Fragment;
569              
570             use strict;
571             use warnings;
572              
573             use Carp;
574              
575             #
576             # Constructor for a `Fragment' object.
577             #
578             sub new ($$$$$) {
579             my $proto = shift or croak;
580             my $class = ref($proto) || $proto;
581             defined(my $offset = shift) or croak "No offset in $class constructor";
582             defined(my $length = shift) or croak "No length in $class constructor";
583             defined(my $mf = shift) or croak "No MF in $class constructor";
584             defined(my $data = shift) or croak "No data in $class constructor";
585              
586             #
587             # Each `Fragment' object contains:
588             # 1. Start octet
589             # 2. End octet
590             # 3. (M)ore (F)ragments flag (`MF' in IPv4; `M' in IPv6)
591             # 4. Payload data
592             #
593             my $self = {
594             START => $offset,
595             END => $offset+$length,
596             MF => $mf,
597             DATA => $data,
598             };
599              
600             bless($self, $class);
601             return $self;
602             }
603              
604             #
605             # Accessor function for start octet value.
606             #
607             sub start ($) {
608             my $self = shift;
609             ref($self) or croak;
610             return $self->{START}
611             }
612              
613             #
614             # Accessor function for end octet value.
615             #
616             sub end ($) {
617             my $self = shift;
618             ref($self) or croak;
619             return $self->{END}
620             }
621              
622             #
623             # Accessor function for MF/M flag.
624             #
625             sub mf ($) {
626             my $self = shift;
627             ref($self) or croak;
628             return $self->{MF}
629             }
630              
631             #
632             # Accessor function for fragment data.
633             #
634             sub data ($) {
635             my $self = shift;
636             ref($self) or croak;
637             return $self->{DATA}
638             }
639              
640             #
641             # Return a string listing a fragment's vital statistics.
642             #
643             sub vitals ($) {
644             my $self = shift;
645             ref($self) or croak;
646             return 'start:'. $self->start .' end:'. $self->end .' mf:'. $self->mf;
647             }
648              
649             ####
650              
651             1;
652              
653             __END__