| 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__ |