File Coverage

blib/lib/Check/NetworkSpans.pm
Criterion Covered Total %
statement 29 405 7.1
branch 0 218 0.0
condition 0 105 0.0
subroutine 10 14 71.4
pod 4 4 100.0
total 43 746 5.7


line stmt bran cond sub pod time code
1             package Check::NetworkSpans;
2              
3 1     1   136802 use 5.006;
  1         4  
4 1     1   6 use strict;
  1         3  
  1         46  
5 1     1   8 use warnings;
  1         2  
  1         66  
6 1     1   755 use Rex::Commands::Gather qw( network_interfaces );
  1         254239  
  1         8  
7 1     1   1640 use Regexp::IPv4 qw($IPv4_re);
  1         1678  
  1         158  
8 1     1   36 use Regexp::IPv6 qw($IPv6_re);
  1         2924  
  1         150  
9 1     1   9 use Scalar::Util qw(looks_like_number);
  1         2  
  1         72  
10 1     1   17 use File::Temp qw( tempdir );
  1         23076  
  1         64  
11 1     1   11 use String::ShellQuote qw(shell_quote);
  1         1468  
  1         57  
12 1     1   10 use JSON qw(decode_json);
  1         10677  
  1         4  
13              
14             =head1 NAME
15              
16             Check::NetworkSpans - See if bidirectional traffic is being seen on spans.
17              
18             =head1 VERSION
19              
20             Version 0.1.0
21              
22             =cut
23              
24             our $VERSION = '0.1.0';
25              
26             =head1 SYNOPSIS
27              
28             use Check::NetworkSpans;
29              
30             my $span_checker = Check::NetworkSpans->new(
31             spans=>[
32             ['em0', 'em1'],
33             ['em2', 'em3'],
34             ],
35             low_packets_to_ignore=>['em2,em3'],
36             );
37              
38             =head1 METHODS
39              
40             =head2 new
41              
42             Initiates the object.
43              
44             - spans :: A array of arrays. Each sub array is a list of interfaces
45             to check. If not defined it will check all interfaces and treat
46             them as one span.
47             - Default :: undef
48              
49             - ignore_IPs :: A array of IPs to ignore.
50             - Default :: undef
51              
52             - auto_ignore :: If true, then will ignore all IP on that machine. Only
53             for the first IP of the interface.
54             - Default :: 1
55              
56             - packets :: Number of packets to gather for a interface for checking.
57             - Default :: 5000
58              
59             - duration :: Number of seconds to limit the run to.
60             - Default :: 60
61              
62             - ports :: Common ports to look for. Anything here will override the defaults.
63             - Default :: [ 22, 53, 80, 88, 135, 389, 443, 445, 3389, 3306, 5432 ]
64              
65             - additional_ports :: Additional ports to look for.
66             - Default :: [ ]
67              
68             - span_names :: Optional name for spans. Name corresponds to index of spans array.
69             - Default :: [ ]
70              
71             my $span_checker = Check::NetworkSpans->new(
72             spans => \@spans,
73             ignore_IPs => \@ignore_IPs,
74             auto_ignore => $auto_ignore,
75             packets => $packets,
76             duration => $duration,
77             ports => \@ports,
78             additional_ports => \@additional_ports,
79             no_packets => 2,
80             no_packets_to_ignore => {},
81             low_packets => 1,
82             low_packets_to_ignore => {},
83             no_streams => 2,
84             no_streams_to_ignore => {},
85             missing_interface => 3,
86             missing_interface_to_ignore => {},
87             );
88              
89             Below are the options controlling alerting and what to ignore.
90              
91             - no_packets :: If the span has no packets.
92             Value :: alert level
93             Default :: 2
94              
95             - no_packets_to_ignore ::
96             Value :: array of spans or span names
97             Default :: []
98              
99             - low_packets :: If the span has fewer packets than the amount specified by packets.
100             Value :: alert level
101             Default :: 1
102              
103             - low_packets_to_ignore :: What to ignore for low_packets.
104             Value :: array of spans or span names
105             Default :: []
106              
107             - no_streams :: No bidirectional TCP/UDP streams were found between IP addresses.
108             Value :: alert level
109             Default :: 2
110              
111             - no_streams_to_ignore :: What to ignore for no_streams.
112             Value :: array of spans or span names
113             Default :: []
114              
115              
116             - missing_interface :: A interface is missing.
117             Value :: alert level
118             Default :: 3
119              
120             - missing_interface_to_ignore :: What to ignore for missing_interface.
121             Value :: array interfaces
122             Default :: []
123              
124             - port_check :: No traffic was found on the expected ports.
125             Value :: alert level
126             Default :: 1
127              
128             - port_check_to_ignore :: What to ignore for port_check.
129             Value :: array of spans or span names
130             Default :: []
131              
132             Levels are as below.
133              
134             - 0 :: OK
135             - 1 :: WARNING
136             - 2 :: ALERT
137             - 3 :: ERROR
138              
139             =cut
140              
141             sub new {
142 0     0 1   my ( $blank, %opts ) = @_;
143              
144             # ensure spans is defined and an array
145 0 0         if ( !defined( $opts{spans} ) ) {
    0          
146 0           die('"spans" is undef');
147             } elsif ( ref( $opts{spans} ) ne 'ARRAY' ) {
148 0           die( '"spans" is defined and is ref "' . ref( $opts{spans} ) . '" instead of ARRAY' );
149             }
150              
151             my $self = {
152             ignore_IPs => [],
153             spans => [],
154             interfaces => [],
155             packets => 5000,
156             duration => 60,
157             warnings => [],
158             ports => [],
159             ports_check => {},
160             span_names => [],
161             no_packets => 2,
162             no_packets_to_ignore => {},
163             low_packets => 1,
164             low_packets_to_ignore => {},
165             no_streams => 2,
166             no_streams_to_ignore => {},
167             down_interface => 2,
168             down_interfaces_to_ignore => {},
169             missing_interface => 3,
170             missing_interface_to_ignore => {},
171             interfaces_missing => [],
172             interfaces_down => {},
173             port_check => 1,
174             port_check_to_ignore => {},
175             debug => $opts{debug},
176 0           };
177 0           bless $self;
178              
179             # suck in alert handling stuff
180 0           my @alerts = ( 'no_packets', 'low_packets', 'no_streams', 'down_interface', 'missing_interface', 'port_check' );
181 0           foreach my $alert_type (@alerts) {
182 0 0         if ( defined( $opts{$alert_type} ) ) {
183 0 0         if ( ref( $opts{$alert_type} ) ne '' ) {
184 0           die( '$opts{' . $alert_type . '} should be ref "" and not ' . ref( $opts{$alert_type} ) );
185             }
186 0 0 0       if ( $opts{$alert_type} ne '0'
      0        
      0        
187             && $opts{$alert_type} ne '1'
188             && $opts{$alert_type} ne '2'
189             && $opts{$alert_type} ne '3' )
190             {
191 0           die( '$opts{' . $alert_type . '} should be either 0, 1, 2, or 3 and not ' . $opts{$alert_type} );
192             }
193 0           $self->{$alert_type} = $opts{$alert_type};
194              
195             } ## end if ( defined( $opts{$alert_type} ) )
196 0 0         if ( defined( $opts{ $alert_type . '_to_ignore' } ) ) {
197 0 0         if ( ref( $opts{ $alert_type . '_to_ignore' } ) ne 'ARRAY' ) {
198             die( '$opts{'
199             . $alert_type
200             . '_to_ignore} should be ref ARRAY and not '
201 0           . ref( $opts{ $alert_type . '_to_ignore' } ) );
202             }
203 0           foreach my $to_ignore ( @{ $opts{ $alert_type . '_to_ignore' } } ) {
  0            
204 0           $self->{ $alert_type . '_to_ignore' }{$to_ignore} = 1;
205             }
206             } ## end if ( defined( $opts{ $alert_type . '_to_ignore'...}))
207             } ## end foreach my $alert_type (@alerts)
208              
209             # get span_names and ensure it is a array
210 0 0 0       if ( defined( $opts{span_names} ) && ref( $opts{span_names} ) eq 'ARRAY' ) {
    0 0        
211 0           $self->{span_names} = $opts{span_names};
212             } elsif ( defined( $opts{span_names} ) && ref( $opts{span_names} ) ne 'ARRAY' ) {
213 0           die( '$opts{span_names} ref is not ARRAY, but "' . ref( $opts{span_names} ) . '"' );
214             }
215              
216             # get packet info and do a bit of sanity checking
217 0 0 0       if ( defined( $opts{packets} ) && looks_like_number( $opts{packets} ) ) {
    0 0        
218 0 0         if ( $opts{packets} < 1 ) {
219 0           die( '$opts{packets} is ' . $opts{packets} . ' which is less than 1' );
220             }
221 0           $self->{packets} = $opts{packets};
222             } elsif ( defined( $opts{packets} ) && !looks_like_number( $opts{packets} ) ) {
223 0           die('$opts{packets} is defined and not a number');
224             }
225              
226             # if ports is set, ensure it is a array and if so process it
227 0 0 0       if ( defined( $opts{ports} ) && ref( $opts{ports} ) ne 'ARRAY' ) {
    0 0        
      0        
228 0           die( '"ports" is defined and is ref "' . ref( $opts{ports} ) . '" instead of ARRAY' );
229             } elsif ( defined( $opts{ports} ) && ref( $opts{ports} ) eq 'ARRAY' && defined( $opts{ports}[0] ) ) {
230 0           foreach my $port ( @{ $opts{ports} } ) {
  0            
231 0 0         if ( ref($port) ne '' ) {
    0          
232 0           die( 'Values for the array ports must be ref type ""... found "' . ref($port) . '"' );
233             } elsif ( !looks_like_number($port) ) {
234 0           die( 'Values for the array ports must be numberic... found "'
235             . $port
236             . '", which does not appear to be' );
237             }
238 0           push( @{ $self->{ports} }, $port );
  0            
239             } ## end foreach my $port ( @{ $opts{ports} } )
240             } else {
241             # defaults if we don't have ports
242 0           push( @{ $self->{ports} }, 22, 53, 80, 88, 135, 389, 443, 445, 3389, 3306, 5432 );
  0            
243             }
244              
245             # if additional_ports is set, ensure it is a array and if so process it
246 0 0 0       if ( defined( $opts{additional_ports} ) && ref( $opts{additional_ports} ) ne 'ARRAY' ) {
    0 0        
      0        
247 0           die( '"additional_ports" is defined and is ref "' . ref( $opts{additional_ports} ) . '" instead of ARRAY' );
248             } elsif ( defined( $opts{additional_ports} )
249             && ref( $opts{additional_ports} ) eq 'ARRAY'
250             && defined( $opts{additional_ports}[0] ) )
251             {
252 0           foreach my $port ( @{ $opts{additional_ports} } ) {
  0            
253 0 0         if ( ref($port) ne '' ) {
    0          
254 0           die( 'Values for the array additional_ports must be ref type ""... found "' . ref($port) . '"' );
255             } elsif ( !looks_like_number($port) ) {
256 0           die( 'Values for the array additional_ports must be numberic... found "'
257             . $port
258             . '", which does not appear to be' );
259             }
260 0           push( @{ $self->{ports} }, $port );
  0            
261             } ## end foreach my $port ( @{ $opts{additional_ports} })
262             } ## end elsif ( defined( $opts{additional_ports} ) &&...)
263              
264 0 0 0       if ( defined( $opts{duration} ) && looks_like_number( $opts{duration} ) ) {
265 0           $self->{duration} = $opts{duration};
266             }
267              
268 0           my $interfaces = network_interfaces;
269             # make sure each specified interface exists
270 0           foreach my $span ( @{ $opts{spans} } ) {
  0            
271 0 0         if ( ref($span) ne 'ARRAY' ) {
272 0           die( 'Values for spans should be a array of interface names... not ref "' . ref($span) . '"' );
273             }
274 0           my $new_span = [];
275 0 0         if ( defined( $span->[0] ) ) {
276 0           foreach my $interface ( @{$span} ) {
  0            
277 0 0         if ( ref($interface) ne '' ) {
    0          
278 0           die( 'interface values in span must be of ref type "" and not ref ' . ref($interface) );
279             } elsif ( !defined( $interfaces->{$interface} ) ) {
280 0           push( @{ $self->{interfaces_missing} }, $interface );
  0            
281             } else {
282 0           push( @{ $self->{interfaces} }, $interface );
  0            
283 0           push( @{$new_span}, $interface );
  0            
284             }
285             } ## end foreach my $interface ( @{$span} )
286             } ## end if ( defined( $span->[0] ) )
287              
288 0           push( @{ $self->{spans} }, $new_span );
  0            
289             } ## end foreach my $span ( @{ $opts{spans} } )
290              
291             # ensure all the ignore IPs are actual IPs
292 0 0         if ( defined( $opts{ignore_IPs} ) ) {
293 0 0         if ( ref( $opts{ignore_IPs} ) ne 'ARRAY' ) {
294 0           die( '"ignore_IPs" is defined and is ref "' . ref( $opts{ignore_IPs} ) . '" instead of ARRAY' );
295             }
296              
297 0           foreach my $ip ( @{ $opts{ignore_IPs} } ) {
  0            
298 0 0 0       if ( $ip !~ /^$IPv6_re$/ && $ip !~ /^$IPv4_re$/ ) {
299 0           die( '"' . $ip . '" does not appear to be a IPv4 or IPv6 IP' );
300             }
301 0           push( @{ $self->{ignore_IPs} }, $ip );
  0            
302             }
303             } ## end if ( defined( $opts{ignore_IPs} ) )
304              
305 0 0         if ( $opts{auto_ignore} ) {
306 0           foreach my $interface ( keys( %{$interfaces} ) ) {
  0            
307 0 0 0       if (
      0        
308             defined( $interfaces->{$interface}{ip} )
309             && ( $interfaces->{$interface}{ip} =~ /^$IPv6_re$/
310             || $interfaces->{$interface}{ip} =~ /^$IPv4_re$/ )
311             )
312             {
313 0           push( @{ $self->{ignore_IPs} }, $interfaces->{$interface}{ip} );
  0            
314             }
315             } ## end foreach my $interface ( keys( %{$interfaces} ) )
316             } ## end if ( $opts{auto_ignore} )
317              
318             # put together list of ports to help
319 0           foreach my $ports ( @{ $self->{ports} } ) {
  0            
320 0           $self->{ports_check}{$ports} = 1;
321             }
322              
323 0           return $self;
324             } ## end sub new
325              
326             =head2 check
327              
328             Runs the check. This will call tshark and then disect that captured PCAPs.
329              
330             my $results = $span_checker->check;
331              
332             use Data::Dumper;
333             print Dumper($results);
334              
335             The returned value is a hash. The keys are as below.
336              
337             - oks :: An array of items that were considered OK.
338              
339             - warnings :: An array of items that were considered warnings.
340              
341             - criticals :: An array of items that were considered criticals.
342              
343             - ignored :: An array of items that were ignored.
344              
345             - status :: Alert status integer.
346              
347             =cut
348              
349             sub check {
350 0     0 1   my $self = $_[0];
351              
352 0           my $filter = '';
353 0 0         if ( $self->{ignore_IPs}[0] ) {
354 0 0         if ( $self->{debug} ) {
355 0           print "DEBUG: Processing \$self->{ignore_IPs} ...\n";
356             }
357 0           my $ignore_IPs_int = 0;
358 0           while ( defined( $self->{ignore_IPs}[$ignore_IPs_int] ) ) {
359 0 0         if ( $ignore_IPs_int > 0 ) {
360 0           $filter = $filter . ' and';
361             }
362 0           $filter = $filter . ' not host ' . $self->{ignore_IPs}[$ignore_IPs_int];
363              
364 0           $ignore_IPs_int++;
365             }
366 0           $filter =~ s/^ //;
367 0 0         if ( $ignore_IPs_int > 0 ) {
368 0           $filter = $filter . ' and not arp';
369             } else {
370 0           $filter = 'not arp';
371             }
372 0 0         if ( $self->{debug} ) {
373 0           print 'DEBUG: Finished generating filter... filter=' . $filter . "\n";
374             }
375             } ## end if ( $self->{ignore_IPs}[0] )
376              
377 0           my $dir = tempdir( CLEANUP => 1 );
378 0           chdir($dir);
379              
380 0           my @span_names;
381 0           foreach my $span ( @{ $self->{spans} } ) {
  0            
382 0           my $span_name = join( ',', @{$span} );
  0            
383 0           push( @span_names, $span_name );
384             my @tshark_args = (
385             'tshark', '-a', 'duration:' . $self->{duration}, '-a',
386 0           'packets:' . $self->{packets}, '-w', $span_name . '.pcap', '-f',
387             $filter
388             );
389 0           foreach my $interface ( @{$span} ) {
  0            
390 0           push( @tshark_args, '-i', $interface );
391             }
392 0 0         if ( $self->{debug} ) {
393 0           print 'DEBUG: calling tshark for span '
394             . $span_name
395             . "\nDEBUG: args... '"
396             . join( '\' \'', @tshark_args ) . "'\n";
397 0           print "DEBUG: calling env...\n";
398 0           system('env');
399 0           system(@tshark_args);
400             } else {
401 0           push( @tshark_args, '-Q' );
402 0           my @tshark_args_quoted;
403 0           my $command = shell_quote(@tshark_args);
404 0           my $tshark_output = `$command 2>&1`;
405             }
406 0 0         if ( $self->{debug} ) {
407 0           print "DEBUG: returned... results... ";
408 0           system('pwd');
409 0           system( '/bin/ls', '-l' );
410             }
411             } ## end foreach my $span ( @{ $self->{spans} } )
412              
413 0           my $results = {
414             'oks' => [],
415             'warnings' => [],
416             'criticals' => [],
417             'errors' => [],
418             'ignored' => [],
419             status => 0,
420             };
421              
422             # process each PCAP into a hash
423 0           my $span_packets = {};
424 0           my $span_int = 0;
425 0           foreach my $span_name (@span_names) {
426 0 0         if ( $self->{debug} ) {
427 0           print 'DEBUG: processing ' . $span_name . ".pcap\n";
428             }
429 0 0         if ( -f $span_name . '.pcap' ) {
430 0           my $pcap_json = `tshark -r "$span_name".pcap -T json -J "ip eth tcp udp" 2> /dev/null`;
431 0 0         if ( $self->{debug} ) {
432 0           print 'DEBUG: dumped ' . $span_name . ".pcap to json\n";
433             }
434 0           eval {
435 0 0         if ( $self->{debug} ) {
436 0           print "DEBUG: processing json\n";
437             }
438 0           my $pcap_data = decode_json($pcap_json);
439 0           $span_packets->{$span_name} = $pcap_data;
440             };
441 0 0         if ($@) {
442 0 0         if ( $self->{debug} ) {
443 0           print 'DEBUG: parsing json failed... ' . $@ . "\n";
444             }
445             push(
446 0           @{ $self->{warnings} },
  0            
447             'Failed to parse PCAP for span "' . $self->get_span_name($span_int) . '"... ' . $@
448             );
449             }
450             } else {
451 0 0         if ( $self->{debug} ) {
452 0           print 'DEBUG: ' . $span_name . ".pcap does not exist\n";
453             }
454 0           push( @{ $self->{warnings} }, 'Failed capture PCAP for "' . $self->get_span_name($span_int) . '"' );
  0            
455             }
456 0           $span_int++;
457             } ## end foreach my $span_name (@span_names)
458              
459 0 0         if ( $self->{debug} ) {
460 0           print "DEBUG: starting processing connection data\n";
461             }
462 0           my $connections = {};
463 0           my $port_connections_per_span = {};
464 0           my $port_connections_per_port = {};
465 0           foreach my $port ( @{ $self->{ports} } ) {
  0            
466 0           $port_connections_per_port->{$port} = 0;
467             }
468 0           my $packet_count = {};
469 0           my $span_packet_count = {};
470 0           foreach my $span_name (@span_names) {
471 0 0         if ( $self->{debug} ) {
472 0           print 'DEBUG: processing connection data for ' . $span_name . "\n";
473             }
474 0           $connections->{$span_name} = {};
475 0           $port_connections_per_span->{$span_name} = 0;
476 0 0 0       if ( defined( $span_packets->{$span_name} ) && ref( $span_packets->{$span_name} ) eq 'ARRAY' ) {
477 0           $span_packet_count->{$span_name} = $#{ $span_packets->{$span_name} } + 1;
  0            
478              
479             # process each packet for
480 0           foreach my $packet ( @{ $span_packets->{$span_name} } ) {
  0            
481 0           eval {
482 0 0 0       if ( defined( $packet->{_source} )
      0        
483             && defined( $packet->{_source}{layers} )
484             && defined( $packet->{_source}{layers}{eth} ) )
485             {
486 0           my $name = '';
487 0           my $proto = '';
488 0           my $dst_ip = '';
489 0           my $dst_port = '';
490 0           my $src_ip = '';
491 0           my $src_port = '';
492              
493             # used for skipping odd broken packets or and broad cast stuff
494 0           my $add_it = 1;
495              
496 0 0         if ( defined( $packet->{_source}{layers}{udp} ) ) {
497 0           $proto = 'udp';
498 0 0         if ( defined( $packet->{_source}{layers}{udp}{'udp.dstport'} ) ) {
499 0           $dst_port = $packet->{_source}{layers}{udp}{'udp.dstport'};
500             } else {
501 0           $add_it = 0;
502             }
503 0 0         if ( defined( $packet->{_source}{layers}{udp}{'udp.srcport'} ) ) {
504 0           $src_port = $packet->{_source}{layers}{udp}{'udp.srcport'};
505             } else {
506 0           $add_it = 0;
507             }
508             } ## end if ( defined( $packet->{_source}{layers}{udp...}))
509 0 0         if ( defined( $packet->{_source}{layers}{tcp} ) ) {
510 0           $proto = 'tcp';
511 0 0         if ( defined( $packet->{_source}{layers}{tcp}{'tcp.dstport'} ) ) {
512 0           $dst_port = $packet->{_source}{layers}{tcp}{'tcp.dstport'};
513             } else {
514 0           $add_it = 0;
515             }
516 0 0         if ( defined( $packet->{_source}{layers}{tcp}{'tcp.srcport'} ) ) {
517 0           $src_port = $packet->{_source}{layers}{tcp}{'tcp.srcport'};
518             } else {
519 0           $add_it = 0;
520             }
521             } ## end if ( defined( $packet->{_source}{layers}{tcp...}))
522 0 0 0       if ( defined( $packet->{_source}{layers}{ip} )
523             && defined( $packet->{_source}{layers}{ip}{'ip.src'} ) )
524             {
525 0           $src_ip = $packet->{_source}{layers}{ip}{'ip.src'};
526             } else {
527 0           $add_it = 0;
528             }
529 0 0 0       if ( defined( $packet->{_source}{layers}{ip} )
530             && defined( $packet->{_source}{layers}{ip}{'ip.dst'} ) )
531             {
532 0           $dst_ip = $packet->{_source}{layers}{ip}{'ip.dst'};
533             } else {
534 0           $add_it = 0;
535             }
536              
537             # save the packet to per port info
538 0 0 0       if ( $add_it && defined( $self->{ports_check}{$dst_port} ) ) {
539 0           $port_connections_per_span->{$span_name}++;
540 0           $port_connections_per_port->{$dst_port}++;
541             }
542 0 0 0       if ( $add_it && defined( $self->{ports_check}{$src_port} ) ) {
543 0           $port_connections_per_span->{$span_name}++;
544 0           $port_connections_per_port->{$src_port}++;
545             }
546              
547 0 0         if ($add_it) {
548 0           $name = $proto . '-' . $src_ip . '%' . $src_port . '-' . $dst_ip . '%' . $dst_port;
549 0           $connections->{$span_name}{$name} = $packet;
550             }
551             } ## end if ( defined( $packet->{_source} ) && defined...)
552             };
553             } ## end foreach my $packet ( @{ $span_packets->{$span_name...}})
554             } else {
555 0           $span_packet_count->{$span_name} = 0;
556             }
557             } ## end foreach my $span_name (@span_names)
558              
559 0           $results->{port_connections_per_span} = $port_connections_per_span;
560 0           $results->{port_connections_per_port} = $port_connections_per_port;
561 0           $results->{packet_count} = $packet_count;
562              
563 0 0         if ( $self->{debug} ) {
564 0           print "DEBUG: checking for bidirectional traffic\n";
565             }
566             # check each span for bi directional traffic traffic
567 0           $span_int = 0;
568 0           foreach my $span_name (@span_names) {
569 0 0         if ( $self->{debug} ) {
570 0           print 'DEBUG: processing traffic data for ' . $span_name . "\n";
571             }
572 0           my $count = 0;
573             # process each connection for the interface looking for matches
574 0           foreach my $packet_name ( keys( %{ $connections->{$span_name} } ) ) {
  0            
575 0           my $packet = $connections->{$span_name}{$packet_name};
576 0 0 0       if (
      0        
      0        
      0        
577             (
578             defined( $packet->{_source}{layers}{ip} )
579             && defined( $packet->{_source}{layers}{ip}{'ip.dst'} )
580             && defined( $packet->{_source}{layers}{ip}{'ip.src'} )
581             )
582             && (
583             (
584             defined( $packet->{_source}{layers}{tcp} )
585             && defined( $packet->{_source}{layers}{tcp}{'tcp.dstport'} )
586             && defined( $packet->{_source}{layers}{tcp}{'tcp.srcport'} )
587             )
588             || ( defined( $packet->{_source}{layers}{udp} )
589             && defined( $packet->{_source}{layers}{udp}{'udp.dstport'} )
590             && defined( $packet->{_source}{layers}{udp}{'udp.srcport'} ) )
591             )
592             )
593             {
594 0           my $reverse_packet_name = '';
595 0           my $dst_port = '';
596 0           my $src_port = '';
597 0           my $proto = '';
598 0           my $dst_ip = $packet->{_source}{layers}{ip}{'ip.dst'};
599 0           my $src_ip = $packet->{_source}{layers}{ip}{'ip.src'};
600              
601 0 0         if ( defined( $packet->{_source}{layers}{udp} ) ) {
602 0           $proto = 'udp';
603 0 0         if ( defined( $packet->{_source}{layers}{udp}{'udp.dstport'} ) ) {
604 0           $dst_port = $packet->{_source}{layers}{udp}{'udp.dstport'};
605             }
606 0 0         if ( defined( $packet->{_source}{layers}{udp}{'udp.srcport'} ) ) {
607 0           $src_port = $packet->{_source}{layers}{udp}{'udp.srcport'};
608             }
609             }
610 0 0         if ( defined( $packet->{_source}{layers}{tcp} ) ) {
611 0           $proto = 'tcp';
612 0 0         if ( defined( $packet->{_source}{layers}{tcp}{'tcp.dstport'} ) ) {
613 0           $dst_port = $packet->{_source}{layers}{tcp}{'tcp.dstport'};
614             }
615 0 0         if ( defined( $packet->{_source}{layers}{tcp}{'tcp.srcport'} ) ) {
616 0           $src_port = $packet->{_source}{layers}{tcp}{'tcp.srcport'};
617             }
618             }
619              
620 0           $reverse_packet_name = $proto . '-' . $dst_ip . '%' . $dst_port . '-' . $src_ip . '%' . $src_port;
621              
622 0           my $found_it = 0;
623 0 0         if ( defined( $connections->{$span_name}{$reverse_packet_name} ) ) {
624 0           $found_it = 1;
625             }
626              
627 0 0         if ($found_it) {
628 0           $count++;
629             }
630             } ## end if ( ( defined( $packet->{_source}{layers}...)))
631             } ## end foreach my $packet_name ( keys( %{ $connections...}))
632              
633             # if count is less than one, then no streams were found
634 0 0         if ( $count < 1 ) {
635 0           my $level = 'oks';
636 0 0         if ( $self->{no_streams} == 1 ) {
    0          
    0          
637 0           $level = 'warnings';
638             } elsif ( $self->{no_streams} == 2 ) {
639 0           $level = 'criticals';
640             } elsif ( $self->{no_streams} == 3 ) {
641 0           $level = 'errors';
642             }
643              
644 0           my $message = 'No TCP/UDP streams found for span ' . $self->get_span_name($span_int);
645              
646 0 0 0       if ( $self->{no_streams_to_ignore}{ $self->get_span_name_for_check($span_int) }
647             || $self->{no_streams_to_ignore}{$span_name} )
648             {
649 0           push( @{ $results->{ignored} }, 'IGNORED - ' . $level . ' - ' . $message );
  0            
650             } else {
651 0           push( @{ $results->{$level} }, $message );
  0            
652             }
653             } else {
654             push(
655 0           @{ $results->{oks} },
  0            
656             'bidirectional TCP/UDP streams, ' . $count . ', found for ' . $self->get_span_name($span_int)
657             );
658             }
659              
660 0           $span_int++;
661             } ## end foreach my $span_name (@span_names)
662              
663 0 0         if ( $self->{debug} ) {
664 0           print "DEBUG: checking for traffic on ports\n";
665             }
666             # ensure we got traffic on the specified ports
667 0           $span_int = 0;
668 0           foreach my $span_name (@span_names) {
669 0 0         if ( $self->{debug} ) {
670 0           print 'DEBUG: processing port data for ' . $span_name . "\n";
671             }
672 0           my $ports_found = 0;
673 0 0         if ( $port_connections_per_span->{$span_name} > 0 ) {
674 0           $ports_found = 1;
675             }
676 0 0         if ( !$ports_found ) {
677 0           my $level = 'oks';
678 0 0         if ( $self->{port_check} == 1 ) {
    0          
    0          
679 0           $level = 'warnings';
680             } elsif ( $self->{port_check} == 2 ) {
681 0           $level = 'criticals';
682             } elsif ( $self->{port_check} == 3 ) {
683 0           $level = 'errors';
684             }
685             my $message
686             = 'no packets for ports '
687 0           . join( ',', @{ $self->{ports} } )
  0            
688             . ' for span '
689             . $self->get_span_name($span_int);
690              
691 0 0 0       if ( $self->{port_check_to_ignore}{ $self->get_span_name_for_check($span_int) }
692             || $self->{port_check_to_ignore}{$span_name} )
693             {
694 0           push( @{ $results->{ignored} }, 'IGNORED - ' . $level . ' - ' . $message );
  0            
695             } else {
696 0           push( @{ $results->{$level} }, $message );
  0            
697             }
698             } else {
699             push(
700 0           @{ $results->{oks} },
701             'ports '
702 0           . join( ',', @{ $self->{ports} } )
703             . ' have '
704 0           . $port_connections_per_span->{$span_name}
705             . ' packets for span '
706             . $self->get_span_name($span_int)
707             );
708             } ## end else [ if ( !$ports_found ) ]
709 0           $span_int++;
710             } ## end foreach my $span_name (@span_names)
711              
712             # check for interfaces with no packets
713 0           $span_int = 0;
714 0           foreach my $span_name (@span_names) {
715 0 0         if ( $span_packet_count->{$span_name} == 0 ) {
716 0           my $level = 'oks';
717 0 0         if ( $self->{no_packets} == 1 ) {
    0          
    0          
718 0           $level = 'warnings';
719             } elsif ( $self->{no_packets} == 2 ) {
720 0           $level = 'criticals';
721             } elsif ( $self->{no_packets} == 3 ) {
722 0           $level = 'errors';
723             }
724 0           my $message = 'span ' . $self->get_span_name($span_int) . ' has no packets';
725 0 0 0       if ( $self->{no_streams_to_ignore}{ $self->get_span_name_for_check($span_int) }
726             || $self->{no_packets_to_ignore}{$span_name} )
727             {
728 0           push( @{ $results->{ignored} }, 'IGNORED - ' . $level . ' - ' . $message );
  0            
729             } else {
730 0           push( @{ $results->{$level} }, $message );
  0            
731             }
732              
733             } ## end if ( $span_packet_count->{$span_name} == 0)
734 0           $span_int++;
735             } ## end foreach my $span_name (@span_names)
736              
737             #check for low packet count on interfaces
738 0           $span_int = 0;
739 0           foreach my $span_name (@span_names) {
740 0 0         if ( $span_packet_count->{$span_name} < $self->{packets} ) {
741 0           my $level = 'oks';
742 0 0         if ( $self->{low_packets} == 1 ) {
    0          
    0          
743 0           $level = 'warnings';
744             } elsif ( $self->{low_packets} == 2 ) {
745 0           $level = 'criticals';
746             } elsif ( $self->{low_packets} == 3 ) {
747 0           $level = 'errors';
748             }
749             my $message
750             = 'span '
751             . $self->get_span_name($span_int)
752             . ' has a packet count of '
753             . $span_packet_count->{$span_name}
754             . ' which is less than the required '
755 0           . $self->{packets};
756 0 0 0       if ( $self->{low_packets_to_ignore}{ $self->get_span_name_for_check($span_int) }
757             || $self->{low_packets_to_ignore}{$span_name} )
758             {
759 0           push( @{ $results->{ignored} }, 'IGNORED - ' . $level . ' - ' . $message );
  0            
760             } else {
761 0           push( @{ $results->{$level} }, $message );
  0            
762             }
763             } else {
764             push(
765 0           @{ $results->{oks} },
766 0           'span ' . $self->get_span_name($span_int) . ' has ' . $span_packet_count->{$span_name} . ' packets'
767             );
768             }
769 0           $span_int++;
770             } ## end foreach my $span_name (@span_names)
771              
772             # check for missing interfaces
773 0 0 0       if ( $#{ $self->{interfaces_missing} } >= 0
  0            
774             && $self->{missing_interface} > 0 )
775             {
776 0           my $level = 'oks';
777 0 0         if ( $self->{missing_interface} == 1 ) {
    0          
    0          
778 0           $level = 'warnings';
779             } elsif ( $self->{missing_interface} == 2 ) {
780 0           $level = 'criticals';
781             } elsif ( $self->{missing_interface} == 3 ) {
782 0           $level = 'errors';
783             }
784              
785             # sort the missing interfaces into ignored and not ignored
786 0           my @ignored_interfaces;
787             my @missing_interfaces;
788 0           foreach my $interface ( @{ $self->{interfaces_missing} } ) {
  0            
789 0 0         if ( defined( $self->{missing_interface_to_ignore}{$interface} ) ) {
790 0           push( @ignored_interfaces, $interface );
791             } else {
792 0           push( @missing_interfaces, $interface );
793             }
794             }
795              
796             # handle ignored missing interfaces
797 0 0         if ( defined( $ignored_interfaces[0] ) ) {
798 0           my $message = 'missing interfaces... ' . join( ',', @ignored_interfaces );
799 0           push( @{ $results->{ignored} }, 'IGNORED - ' . $level . ' - ' . $message );
  0            
800             }
801              
802             # handle not ignored missing interfaces
803 0 0         if ( defined( $ignored_interfaces[0] ) ) {
804 0           my $message = 'missing interfaces... ' . join( ',', @missing_interfaces );
805 0           push( @{ $results->{$level} }, $message );
  0            
806             }
807             } else {
808 0           push( @{ $results->{oks} }, 'no missing interfaces' );
  0            
809             }
810              
811             # sets the final status
812             # initially set to 0, OK
813 0 0         if ( defined( $results->{errors}[0] ) ) {
    0          
    0          
814 0           $results->{status} = 3;
815             } elsif ( defined( $results->{alerts}[0] ) ) {
816 0           $results->{status} = 2;
817             } elsif ( defined( $results->{warnings}[0] ) ) {
818 0           $results->{status} = 1;
819             }
820              
821 0           return $results;
822             } ## end sub check
823              
824             =head2 get_span_name
825              
826             Returns span name for display purposes.
827              
828             =cut
829              
830             sub get_span_name {
831 0     0 1   my $self = $_[0];
832 0           my $span_int = $_[1];
833              
834 0 0         if ( !defined($span_int) ) {
835 0           return 'undef';
836             }
837              
838 0 0         if ( !defined( $self->{spans}[$span_int] ) ) {
839 0           return 'undef';
840             }
841              
842 0           my $name = join( ',', @{ $self->{spans}[$span_int] } );
  0            
843 0 0 0       if ( defined( $self->{span_names}[$span_int] ) && $self->{span_names}[$span_int] ne '' ) {
844 0           $name = $self->{span_names}[$span_int] . '(' . $name . ')';
845             }
846              
847 0           return $name;
848             } ## end sub get_span_name
849              
850             =head2 get_span_name_for_check
851              
852             Returns span name for check purposes.
853              
854             =cut
855              
856             sub get_span_name_for_check {
857 0     0 1   my $self = $_[0];
858 0           my $span_int = $_[1];
859              
860 0 0         if ( !defined($span_int) ) {
861 0           return 'undef';
862             }
863              
864 0 0         if ( !defined( $self->{spans}[$span_int] ) ) {
865 0           return 'undef';
866             }
867              
868 0 0 0       if ( defined( $self->{span_names}[$span_int] ) && $self->{span_names}[$span_int] ne '' ) {
869 0           return $self->{span_names}[$span_int];
870             }
871              
872 0           return join( ',', @{ $self->{spans}[$span_int] } );
  0            
873             } ## end sub get_span_name_for_check
874              
875             =head1 AUTHOR
876              
877             Zane C. Bowers-Hadley, C<< >>
878              
879             =head1 BUGS
880              
881             Please report any bugs or feature requests to C, or through
882             the web interface at L. I will be notified, and then you'll
883             automatically be notified of progress on your bug as I make changes.
884              
885              
886              
887              
888             =head1 SUPPORT
889              
890             You can find documentation for this module with the perldoc command.
891              
892             perldoc Check::NetworkSpans
893              
894              
895             You can also look for information at:
896              
897             =over 4
898              
899             =item * RT: CPAN's request tracker (report bugs here)
900              
901             L
902              
903             =item * CPAN Ratings
904              
905             L
906              
907             =item * Search CPAN
908              
909             L
910              
911             =back
912              
913              
914             =head1 ACKNOWLEDGEMENTS
915              
916              
917             =head1 LICENSE AND COPYRIGHT
918              
919             This software is Copyright (c) 2024 by Zane C. Bowers-Hadley.
920              
921             This is free software, licensed under:
922              
923             The GNU General Public License, Version 2, June 1991
924              
925              
926             =cut
927              
928             1; # End of Check::NetworkSpans