File Coverage

blib/lib/Data/Netflow.pm
Criterion Covered Total %
statement 131 139 94.2
branch 21 32 65.6
condition 18 31 58.0
subroutine 13 14 92.8
pod 3 4 75.0
total 186 220 84.5


line stmt bran cond sub pod time code
1             package Data::Netflow;
2              
3 3     3   59170 use 5.006;
  3         9  
4 3     3   13 use strict;
  3         4  
  3         53  
5 3     3   13 use warnings;
  3         8  
  3         78  
6 3     3   910 use Time::HiRes qw(gettimeofday);
  3         3010  
  3         11  
7 3     3   1492 use Socket qw( inet_pton inet_ntop inet_ntoa AF_INET6 AF_INET);
  3         8150  
  3         524  
8 3     3   26 use List::Util qw(any);
  3         6  
  3         252  
9              
10 3     3   19 use Carp;
  3         5  
  3         137  
11 3     3   1272 use Data::Dumper;
  3         18709  
  3         4172  
12              
13             my %Ls = (
14             1 => "C",
15             2 => "n",
16             4 => "N",
17             );
18              
19             =head1 NAME
20              
21             Data::Netflow - Module to process binary netflow data (v5 and v9)
22              
23             =head1 VERSION
24              
25             Version 0.02
26              
27             =cut
28              
29             our $VERSION = '0.02';
30              
31             =head1 SYNOPSIS
32              
33             Module to create netflow binary data from text data
34              
35             use Data::Netflow;
36             use IO::Socket;
37              
38             my $sock_udp = IO::Socket::INET->new(
39             Proto => 'udp',
40             PeerPort => 9995,
41             PeerAddr => '127.0.0.1',
42             ) or die "Could not create UDP socket: $!\n";
43              
44             my $TemplateV9 = {
45             'FlowSetId' => 0,
46             'TemplateId' => 300,
47             'Fields' => [
48             { 'Length' => 4, 'Id' => 1 }, # octetDeltaCount
49             { 'Length' => 4, 'Id' => 2 }, # packetDeltaCount
50             { 'Length' => 1, 'Id' => 4 }, # protocolIdentifier
51             { 'Length' => 1, 'Id' => 6 }, # tcp flags
52             { 'Length' => 2, 'Id' => 7 }, # sourceTransportPort
53             { 'Length' => 4, 'Id' => 8 }, # sourceIPv4Address
54             { 'Length' => 2, 'Id' => 11 }, # destinationTransportPort
55             { 'Length' => 4, 'Id' => 12 }, # destinationIPv4Address
56             { 'Length' => 4, 'Id' => 21 }, # last switched
57             { 'Length' => 4, 'Id' => 22 }, # first switched
58             ],
59             }
60              
61             my $Header = {
62             Version => 9,
63             SysUptime => int ( uptime() *1000 ),
64             };
65              
66              
67             my @flow;
68             my @tmp = qw( 5 8126 17 0 22 10.2.1.1 5365 10.2.1.254 ) ;
69             my $uptime = int ( (uptime()- $back ) *1000 );
70             push @tmp , $uptime + 5;
71             push @tmp , $uptime;
72             push @flow , \@tmp;
73              
74             my $encoded = Data::Netflow::encodeV9($Header, $TemplateV9 ,\@flow);
75             $sock_udp->send( $encoded );
76              
77              
78              
79             =head1 EXPORT
80              
81             encodeV5
82             encodeV9
83              
84             =head1 SUBROUTINES/METHODS
85              
86              
87             =head2 decode
88              
89             decode netflow data
90              
91             =cut
92              
93             sub decode
94             {
95 2     2 1 15 my ( $data, $byname ) = @_;
96 2         11 my $version = unpack 'n', $data;
97 2         5 my @flows;
98 2 100       17 if ( $version == 5 )
    50          
99             {
100 1         35 my %data_id = (
101             1 => 'SrcAddr',
102             2 => 'DstAddr',
103             3 => 'NextHop',
104             4 => 'InputInt',
105             5 => 'OutputInt',
106             6 => 'Packets',
107             7 => 'Octets',
108             8 => 'StartTime',
109             9 => 'EndTime',
110             10 => 'SrcPort',
111             11 => 'DstPort',
112             12 => 'Padding',
113             13 => 'TCP Flags',
114             14 => 'Protocol',
115             15 => 'IP ToS',
116             16 => 'SrcAS',
117             17 => 'DstAS',
118             18 => 'SrcMask',
119             19 => 'DstMask',
120             20 => 'Padding',
121             );
122 1         13 my %headers_id = (
123             1 => 'version',
124             2 => 'count',
125             3 => 'SysUptime',
126             4 => 'unix_secs',
127             5 => 'unix_nsecs',
128             6 => 'flow_sequence',
129             7 => 'engine_type',
130             8 => 'engine_id',
131             9 => 'sampling_interval'
132             );
133 1         12 my %headers_name = reverse %headers_id;
134 1         5 my $header = substr $data, 0, 24, '';
135 1         7 my @headers = unpack 'n2N4CCn', $header;
136 1         13 $headers_name{$headers_id{$_ + 1}} = $headers[$_] foreach ( 0 .. 8 );
137 1         5 for ( 1 .. $headers[1] )
138             {
139 2         8 my $flow_data = substr $data, 0, 48, '';
140 2         13 my @flow = unpack 'NNNnnNNNNnnCCCCnnCCn', $flow_data;
141 2         5 my %data_name;
142 2 50       25 if ( $byname )
143             {
144 0         0 %data_name = reverse %data_id;
145             }
146             else
147             {
148 2         29 %data_name = %data_id;
149             }
150 2         10 foreach my $idx ( 0 .. 19 )
151             {
152 40 100       57 if ( $idx <= 2 )
153             {
154 6 50       11 if ( $byname )
155             {
156 0         0 $data_name{$data_id{$idx + 1}} = inet_ntop( AF_INET, pack 'N', $flow[$idx] );
157             }
158             else
159             {
160 6         37 $data_name{$idx + 1} = inet_ntop( AF_INET, pack 'N', $flow[$idx] );
161             }
162             }
163             else
164             {
165 34 50       40 if ( $byname ) {$data_name{$data_id{$idx + 1}} = $flow[$idx];}
  0         0  
166             else
167             {
168 34         55 $data_name{$idx + 1} = $flow[$idx];
169             }
170             }
171             }
172 2         5 delete $data_name{Padding};
173 2         5 push @flows, \%data_name;
174             }
175 1         7 return \%headers_name, \@flows;
176             }
177             elsif ( $version == 9 )
178             {
179 1         8 my %headers_id = (
180             1 => 'version',
181             2 => 'count',
182             3 => 'SysUptime',
183             4 => 'unix_secs',
184             5 => 'flow_sequence',
185             6 => 'engine_id',
186             );
187 1         5 my %headers_name = reverse %headers_id;
188 1         18 my $header = substr $data, 0, 20, '';
189 1         5 my @headers = unpack 'n2N4', $header;
190 1         7 $headers_name{$headers_id{$_ + 1}} = $headers[$_] foreach ( 0 .. 5 );
191 1         6 my %templates;
192             my $flowser_id;
193 1         0 my $field_count;
194 1         3 my ( $flowset_id, $flowset_length ) = unpack 'n2', $data;
195              
196 1 50       4 if ( $flowset_id == 0 )
197             {
198 1         3 my $record = substr $data, 0, $flowset_length, '';
199 1         2 substr $record, 0, 4, '';
200             ## we are in template records
201 1         2 my ( $flowser_id, $field_count ) = unpack 'n2', substr( $record, 0, 4, '' );
202 1         2 my @tmp;
203 1         2 for my $idx ( 1 .. $field_count )
204             {
205 10         17 my ( $id, $len ) = unpack 'n2', ( substr( $record, 0, 4, '' ) );
206 10         19 push @tmp, "$id,$len";
207             }
208 1         3 $templates{$flowser_id} = \@tmp;
209 1         3 ( $flowset_id, $flowset_length ) = unpack 'n2', ( substr( $data, 0, 4, '' ) );
210             }
211              
212 1 50       4 if ( $flowset_id != 0 )
213             {
214             ## we are in data records
215 1         2 for my $nbr ( 1 .. ( $headers_name{count} - 1 ) )
216             {
217 2         3 my %record;
218 2 50       5 if ( exists $templates{$flowset_id} )
219             {
220 2         3 foreach my $item ( @{$templates{$flowset_id}} )
  2         3  
221             {
222 20         41 my ( $i, $l ) = split ',', $item;
223 20 50 33     53 next if ( !defined $i || !defined $l );
224 20 100   70   48 if ( any {$_ == $i} qw(8 12 15 18) )
  70 50       97  
225             {
226 4         22 $record{$i} = inet_ntop( AF_INET, ( substr( $data, 0, $l, '' ) ) );
227             }
228 48     48   55 elsif ( any {$_ == $i} qw(27 28 62) )
229             {
230 0         0 $record{$i} = inet_ntop( AF_INET6, ( substr( $data, 0, $l, '' ) ) );
231             }
232             else
233             {
234 16         54 $record{$i} = unpack $Ls{$l}, ( substr( $data, 0, $l, '' ) );
235             }
236             }
237 2         4 push @flows, \%record;
238             }
239             }
240             }
241 1         6 return \%headers_name, \@flows;
242             }
243             else
244             {
245 0         0 croak "Version $version not supported";
246             }
247              
248             }
249              
250             =head2 encodeV9
251              
252             encode data for netflow version 9 with template
253              
254             =cut
255              
256             sub encodeV9
257             {
258 1     1 1 105 my ( $header, $template, $flowArrayRef ) = @_;
259 1         2 my @flows = @$flowArrayRef;
260 1         2 my $Id = $template->{Fields};
261 1         3 my $out;
262             ### start header ###
263 1   33     4 $header->{SysUptime} //= int uptime() * 1000;
264 1   33     7 $header->{TicksMS} //= time;
265 1   50     5 $header->{PackageNum} = ( ( $header->{PackageNum} // 0 ) + 1 ) % 0xFFFFFFFF;
266 1         3 $header->{Count} = 1 + scalar @flows;
267 1         2 $header->{SourceId} += 0;
268 1         2 $out = pack "n2N4", @{$header}{qw{Version Count SysUptime TicksMS PackageNum SourceId}};
  1         7  
269             ### end header ###
270              
271             ### start template ###
272 1         2 $template->{FieldsCount} = scalar @$Id;
273 1         3 $template->{Length} = 8 + ( $template->{FieldsCount} * 2 * 2 );
274 1         2 my @template_data = ( $template->{FlowSetId}, $template->{Length}, $template->{TemplateId}, $template->{FieldsCount} );
275 1         2 foreach my $f ( sort {$a->{Id} <=> $b->{Id}} @{$template->{Fields}} )
  19         30  
  1         5  
276             {
277 10         15 push @template_data, $f->{Id}, $f->{Length};
278             }
279 1         5 $out .= pack "n*", @template_data;
280             ### end template ###
281              
282 1         1 my $packedData;
283 1         2 foreach my $flow ( @flows )
284             {
285 2         5 foreach my $fieldsIdx ( 0 .. $#$flow )
286             {
287 20         31 my $L = $Ls{$Id->[$fieldsIdx]{Length}};
288 20 100 100     46 if ( $Id->[$fieldsIdx]{Id} == 8 || $Id->[$fieldsIdx]{Id} == 12 )
289             {
290 4 50       23 $packedData .= $flow->[$fieldsIdx] =~ /:/ ? inet_pton( AF_INET6, $flow->[$fieldsIdx] ) : inet_pton( AF_INET, $flow->[$fieldsIdx] );
291             }
292             else
293             {
294 16         35 $packedData .= pack $L, $flow->[$fieldsIdx];
295             }
296             }
297             }
298 1         4 $out .= pack "n2", $template->{TemplateId}, ( length( $packedData ) + 4 );
299 1         2 $out .= $packedData;
300 1         3 return $out;
301             }
302              
303             =head2 encodeV5
304              
305             encode data for netflow version 5
306              
307             =cut
308              
309             sub encodeV5
310             {
311 1     1 1 221 my ( $header, $template, $flowArrayRef ) = @_;
312 1         4 my @flows = @$flowArrayRef;
313 1         2 my $Id = $template->{Fields};
314 1   33     4 $header->{SysUptime} //= int uptime() * 1000;
315 1         8 ( $header->{UnixSecs}, $header->{UnixNsecs} ) = ( gettimeofday );
316 1   50     7 $header->{FlowSeq} //= 0;
317 1   50     5 $header->{EngineType} //= 0;
318 1   50     7 $header->{EngineId} //= 0;
319 1   50     25 $header->{SamplingInt} //= 0;
320              
321 1         3 $header->{Count} = scalar @flows;
322 1         5 my $out = pack "n2N4CCn", @{$header}{qw{Version Count SysUptime UnixSecs UnixNsecs FlowSeq EngineType EngineId SamplingInt}};
  1         12  
323              
324 1         3 my $packedData;
325 1         4 foreach my $flow ( @flows )
326             {
327 2         8 foreach my $fieldsIdx ( 0 .. $#$flow )
328             {
329 40         416 my $L = $Ls{$Id->[$fieldsIdx]{Length}};
330 40 100 100     283 if ( $Id->[$fieldsIdx]{Id} == 1 || $Id->[$fieldsIdx]{Id} == 2 || $Id->[$fieldsIdx]{Id} == 3 )
      100        
331             {
332 6 50       49 $packedData .= $flow->[$fieldsIdx] =~ /:/ ? inet_pton( AF_INET6, $flow->[$fieldsIdx] ) : inet_pton( AF_INET, $flow->[$fieldsIdx] );
333             }
334             else
335             {
336 34         106 $packedData .= pack $L, $flow->[$fieldsIdx];
337             }
338             }
339             }
340 1         5 $out .= $packedData;
341 1         6 return $out;
342             }
343              
344             sub uptime
345             {
346             return (
347             split /\s/,
348 0     0 0   do {local ( @ARGV, $/ ) = '/proc/uptime'; <>}
  0            
  0            
349             )[0];
350             }
351              
352             =head1 AUTHOR
353              
354             DULAUNOY Fabrice, C<< >>
355              
356             =head1 BUGS
357              
358             Please report any bugs or feature requests to C, or through
359             the web interface at L. I will be notified, and then you'll
360             automatically be notified of progress on your bug as I make changes.
361              
362              
363             =head1 TODO
364              
365             decode V9 for multiple data set (and multiple template )
366             decode V9 return by id or by name (if flag) like for V5
367             A single encode (detect version by the header)
368             IPFIX (maybe)
369              
370             =head1 SUPPORT
371              
372             You can find documentation for this module with the perldoc command.
373              
374             perldoc Data::Netflow
375              
376             For the netflow format:
377              
378             Version 5:
379              
380              
381             Version 9:
382              
383             https://www.ietf.org/rfc/rfc3954.txt
384              
385             You can also look for information at:
386              
387             =over 4
388              
389             =item * RT: CPAN's request tracker (report bugs here)
390              
391             L
392              
393             =item * AnnoCPAN: Annotated CPAN documentation
394              
395             L
396              
397             =item * CPAN Ratings
398              
399             L
400              
401             =item * Search CPAN
402              
403             L
404              
405             =back
406              
407              
408             =head1 ACKNOWLEDGEMENTS
409              
410              
411             =head1 LICENSE AND COPYRIGHT
412              
413             Copyright 2017 DULAUNOY Fabrice.
414              
415             This program is free software; you can redistribute it and/or modify it
416             under the terms of the the Artistic License (2.0). You may obtain a
417             copy of the full license at:
418              
419             L
420              
421             Any use, modification, and distribution of the Standard or Modified
422             Versions is governed by this Artistic License. By using, modifying or
423             distributing the Package, you accept this license. Do not use, modify,
424             or distribute the Package, if you do not accept this license.
425              
426             If your Modified Version has been derived from a Modified Version made
427             by someone other than you, you are nevertheless required to ensure that
428             your Modified Version complies with the requirements of this license.
429              
430             This license does not grant you the right to use any trademark, service
431             mark, tradename, or logo of the Copyright Holder.
432              
433             This license includes the non-exclusive, worldwide, free-of-charge
434             patent license to make, have made, use, offer to sell, sell, import and
435             otherwise transfer the Package with respect to any patent claims
436             licensable by the Copyright Holder that are necessarily infringed by the
437             Package. If you institute patent litigation (including a cross-claim or
438             counterclaim) against any party alleging that the Package constitutes
439             direct or contributory patent infringement, then this Artistic License
440             to you shall terminate on the date that such litigation is filed.
441              
442             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
443             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
444             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
445             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
446             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
447             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
448             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
449             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
450              
451              
452             =cut
453              
454             1; # End of Data::Netflow