File Coverage

blib/lib/File/PCAP/ACAP2PCAP.pm
Criterion Covered Total %
statement 79 90 87.7
branch 26 38 68.4
condition 1 3 33.3
subroutine 18 21 85.7
pod 2 2 100.0
total 126 154 81.8


line stmt bran cond sub pod time code
1             package File::PCAP::ACAP2PCAP;
2              
3 3     3   217318 use 5.006;
  3         43  
4 3     3   21 use strict;
  3         6  
  3         71  
5 3     3   13 use warnings;
  3         5  
  3         108  
6              
7 3     3   1318 use File::PCAP::Writer;
  3         9  
  3         93  
8 3     3   2162 use Time::Local;
  3         7246  
  3         239  
9              
10             =head1 NAME
11              
12             File::PCAP::ACAP2PCAP - convert ASA capture to PCAP
13              
14             =head1 VERSION
15              
16             Version v0.1.1
17              
18             =cut
19              
20 3     3   27 use version; our $VERSION = qv('v0.1.1');
  3         8  
  3         19  
21              
22             =head1 SYNOPSIS
23              
24             This module converts Cisco ASA packet capture outputs to PCAP files.
25              
26             use File::PCAP::ACAP2PCAP;
27              
28             my $a2p = File::PCAP::ACAP2PCAP->new( $args );
29              
30             $a2p->parse(\*STDIN);
31              
32             =head1 SUBROUTINES/METHODS
33              
34             =head2 new( $args )
35              
36             Creates a new object, takes a hash reference as argument with
37             the following keys:
38              
39             my $a2p = File::PCAP::ACAP2PCAP( {
40             dlt => $dlt, # data link type, see below
41             output => $fname, # filename for PCAP output
42             startday => $day, # day the first packet was captured
43             } );
44              
45             The data link type is put in the PCAP global header.
46             It defaults to 1 (Ethernet).
47             There are some versions of Cisco software that output raw IP headers.
48             For these use 101 (Raw IP) and for more information on data link types, see
49             L.
50              
51             The startday argument allows to provide a different day for the first
52             captured packet, it defaults to the current day.
53             All timestamps in the generated packet capture are based on 00:00 UTC of
54             this day. If there are timestamps that are smaller than previously parsed
55             timestamps, a new day is assumed and the base value for the timestamps is
56             increased by 86400 (one day). You may want to provide this argument with
57             a date in the past when a packet capture runs over midnight.
58              
59             =cut
60              
61             sub new {
62 4     4 1 2395 my ($self,$args) = @_;
63 4   33     30 my $type = ref($self) || $self;
64            
65 4         10 my $now = time;
66              
67 4         9 my $fpwargs = {};
68 4 100       15 if (exists $args->{dlt}) {
69 2         6 $fpwargs->{dlt} = $args->{dlt};
70             }
71 4 50       15 if (exists $args->{output}) {
72 4         14 $fpwargs->{fname} = $args->{output};
73             }
74             else {
75 0         0 $fpwargs->{fname} = 'asa.pcap';
76             }
77            
78 4         28 my $fpw = File::PCAP::Writer->new($fpwargs);
79            
80 4         22 $self = bless {
81             state => 'unknown',
82             sot => _get_startday($now,$args), # start of today
83             last_sec => 0,
84             now => $now,
85             fpw => $fpw,
86             packet_bytes => "",
87             }, $type;
88 4         255 return $self;
89             } # new()
90              
91             =head2 parse( $fd )
92              
93             This function does the parsing of the ASA output from an IO stream.
94              
95             To parse STDIN, you would do something like the following:
96              
97             $a2p->parse(\*STDIN);
98              
99             To parse a file given by name, you open it and take the file handle:
100              
101             if (open(my $input,'<',$filename)) {
102             $a2p->parse($input);
103             close $input;
104             }
105              
106             To write the packets into the PCAP file this function uses
107             L<< File::PCAP::Writer->packet()|File::PCAP::Writer >>.
108              
109             =cut
110              
111             sub parse {
112 2     2 1 167 my ($self,$fd) = @_;
113            
114 2         89 while (my $line = <$fd>) {
115 40         97 $self->_read_line($line);
116             }
117 2         12 $self->_write_packet();
118             } # parse()
119              
120             # internal functions and variables
121              
122             my $r_strt = qr/^([0-9]+) packets? captured$/;
123             my $r_empt = qr/^$/;
124             my $r_dscr = qr/^\s*([0-9]+): ([0-9]{2}):([0-9]{2}):([0-9]{2})\.([0-9]+)\s+(.+)$/;
125             my $r_mdsc = qr/^\s+(\S.*)$/;
126             my $r_stop = qr/^([0-9]+) packets? shown$/;
127             my $r_dump = qr/^(0x[0-9a-f]+)\s+([0-9a-f][0-9a-f ]{38})\s{8}(.+)$/;
128              
129             # The function _get_startday() determines the date that the first read
130             # captured package should be in. It defaults to the current day.
131             #
132             # Beware, this assumes UTZ as timezone.
133             #
134             sub _get_startday {
135 4     4   13 my ($now,$args) = @_;
136 4         35 my @today = gmtime($now);
137 4         11 $today[0] = $today[1] = $today[2] = 0;
138 4 100       16 if (my $startday = $args->{startday}) {
139 3 50       33 if ($startday =~ /^(\d{4})-?(\d{2})-?(\d{2})$/) {
140 3         18 $today[5] = $1 - 1900;
141 3         10 $today[4] = $2 -1;
142 3         10 $today[3] = $3;
143             }
144             }
145 4         20 return timegm(@today);
146             } # _get_startday()
147              
148             # The function _read_line() reads the input one line at a time and
149             # decides what to do with that line.
150             #
151             # The basic knowledge (a state machine driven by the input line) is encoded
152             # in the hash $states.
153             #
154             sub _read_line {
155 40     40   107 my ($self,$line) = @_;
156             my $states = {
157             unknown => sub {
158 11 100   11   66 return ($line =~ $r_strt) ? $self->_l_strt($1)
159             : 'unknown'
160             ;
161             },
162             strt => sub {
163 3 50   3   55 return ($line =~ $r_empt) ? 'strt'
    100          
164             : ($line =~ $r_dscr) ? $self->_l_dscr($1,$2,$3,$4,$5,$6)
165             : 'unknown'
166             ;
167             },
168             dscr => sub {
169 5 50   5   91 return ($line =~ $r_dscr) ? $self->_l_dscr($1,$2,$3,$4,$5,$6)
    50          
    100          
    50          
    50          
170             : ($line =~ $r_mdsc) ? $self->_l_mdsc($1)
171             : ($line =~ $r_dump) ? $self->_l_dump($1,$2,$3)
172             : ($line =~ $r_stop) ? $self->_l_stop()
173             : ($line =~ $r_empt) ? 'dump'
174             : 'unknown'
175             ;
176             },
177             dump => sub {
178 21 0   21   130 return ($line =~ $r_dump) ? $self->_l_dump($1,$2,$3)
    50          
    100          
179             : ($line =~ $r_dscr) ? $self->_l_dscr($1,$2,$3,$4,$5,$6)
180             : ($line =~ $r_stop) ? $self->_l_stop()
181             : 'unknown'
182             ;
183             },
184             stop => sub {
185 0     0   0 return 'unknown';
186             },
187 40         325 };
188 40         94 my $state = $self->{state};
189 40         79 $self->{state} = $states->{$state}->($line);
190 40 50       434 if ($self->{debug}) {
191 0         0 print "$state -> $self->{state}: $line";
192             }
193             } # _read_line()
194              
195             # The _l_*() functions are called, when a corresponding regular
196             # expression $_r_* matches the input.
197             #
198             sub _l_strt {
199 2     2   11 my ($self,$count) = @_;
200 2         7 $self->{captured} = $count;
201 2         6 return 'strt';
202             } # _l_strt()
203              
204             sub _l_dscr {
205 5     5   36 my ($self,$nr,$hour,$min,$sec,$usec,$dscr) = @_;
206 5         19 $self->_write_packet();
207 5         13 $self->{packet_number} = $nr;
208 5         9 $self->{packet_dscr} = $dscr;
209 5         20 $self->{packet_secs} = $self->{sot} + 3600 * $hour + 60 * $min + $sec;
210 5         11 $self->{packet_usec} = $usec;
211 5         15 return 'dscr';
212             } # _l_dscr()
213              
214             sub _l_mdsc {
215 0     0   0 my ($self,$dscr) = @_;
216 0         0 $self->{packet_dscr} .= " $dscr";
217 0         0 return 'dscr';
218             } # _l_mdsc()
219              
220             sub _l_dump {
221 22     22   92 my ($self,$offset,$hex,$printable) = @_;
222 22         45 my $bytes = $hex;
223 22         92 $bytes =~ s/ //g;
224 22         44 my $len = length $self->{packet_bytes};
225 22 50       59 if ($len == 2 * hex($offset)) {
226 22         45 $self->{packet_bytes} .= $bytes;
227             } else {
228 0         0 $len = sprintf( "0x%x", $len / 2);
229 0         0 my $pn = $self->{packet_number};
230 0         0 die "Bad things happened: have $len bytes and offset is $offset in packet $pn";
231             }
232 22         51 return 'dump';
233             } # _l_dump()
234              
235             sub _l_stop {
236 0     0   0 _write_packet(@_);
237 0         0 return 'stop';
238             } # _l_stop()
239              
240             # _write_packet() writes the actual datagram data including the packet
241             # header at the end of the PCAP file.
242             #
243             sub _write_packet {
244 7     7   14 my ($self) = @_;
245 7 100       24 if (my $len = length($self->{packet_bytes})) {
246 5 100       17 if ($self->{last_sec} > $self->{packet_secs}) {
247             # we have probably crossed midnight
248 1         3 $self->{packet_secs} += 86400;
249 1         3 $self->{sot} += 86400;
250             }
251 5         10 my $sec = $self->{packet_secs};
252 5         13 my $usec = $self->{packet_usec};
253 5         27 my $buf = pack('H*', $self->{packet_bytes});
254 5         12 $len /= 2;
255 5         23 $self->{fpw}->packet($sec,$usec,$len,$len,$buf);
256 5         20 $self->{packet_bytes} = "";
257 5         16 $self->{last_sec} = $sec;
258             }
259             } # _write_packet()
260              
261             =head1 SEE ALSO
262              
263             Libpcap File Format
264             L
265              
266             Link-Layer Header Types
267             L
268              
269             =head1 AUTHOR
270              
271             Mathias Weidner, C<< >>
272              
273             =head1 BUGS
274              
275             Please report any bugs or feature requests to C, or through
276             the web interface at L. I will be notified, and then you'll
277             automatically be notified of progress on your bug as I make changes.
278              
279             =head1 SUPPORT
280              
281             You can find documentation for this module with the perldoc command.
282              
283             perldoc File::PCAP
284              
285             You can also look for information at:
286              
287             =over 4
288              
289             =item * RT: CPAN's request tracker (report bugs here)
290              
291             L
292              
293             =item * AnnoCPAN: Annotated CPAN documentation
294              
295             L
296              
297             =item * CPAN Ratings
298              
299             L
300              
301             =item * Search CPAN
302              
303             L
304              
305             =back
306              
307             =head1 LICENSE AND COPYRIGHT
308              
309             Copyright 2017 Mathias Weidner.
310              
311             This program is free software; you can redistribute it and/or modify it
312             under the terms of the the Artistic License (2.0). You may obtain a
313             copy of the full license at:
314              
315             L
316              
317             Any use, modification, and distribution of the Standard or Modified
318             Versions is governed by this Artistic License. By using, modifying or
319             distributing the Package, you accept this license. Do not use, modify,
320             or distribute the Package, if you do not accept this license.
321              
322             If your Modified Version has been derived from a Modified Version made
323             by someone other than you, you are nevertheless required to ensure that
324             your Modified Version complies with the requirements of this license.
325              
326             This license does not grant you the right to use any trademark, service
327             mark, tradename, or logo of the Copyright Holder.
328              
329             This license includes the non-exclusive, worldwide, free-of-charge
330             patent license to make, have made, use, offer to sell, sell, import and
331             otherwise transfer the Package with respect to any patent claims
332             licensable by the Copyright Holder that are necessarily infringed by the
333             Package. If you institute patent litigation (including a cross-claim or
334             counterclaim) against any party alleging that the Package constitutes
335             direct or contributory patent infringement, then this Artistic License
336             to you shall terminate on the date that such litigation is filed.
337              
338             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
339             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
340             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
341             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
342             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
343             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
344             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
345             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
346              
347             =cut
348              
349             # vim: set sw=4 ts=4 et:
350             1; # End of File::PCAP