File Coverage

blib/lib/Net/DNS/Check.pm
Criterion Covered Total %
statement 33 252 13.1
branch 0 104 0.0
condition 0 18 0.0
subroutine 11 27 40.7
pod 12 12 100.0
total 56 413 13.5


line stmt bran cond sub pod time code
1             package Net::DNS::Check;
2              
3 1     1   47347 use strict;
  1         2  
  1         38  
4 1     1   5 use vars qw($VERSION $AUTOLOAD);
  1         2  
  1         65  
5              
6             $VERSION = '0.45';
7              
8 1     1   5 use Carp;
  1         7  
  1         78  
9 1     1   1007 use Net::DNS;
  1         121612  
  1         106  
10 1     1   872 use Net::DNS::Resolver::Recurse;
  1         2045  
  1         32  
11 1     1   662 use Net::DNS::Check::Config;
  1         2  
  1         27  
12 1     1   515 use Net::DNS::Check::HostsList;
  1         3  
  1         29  
13 1     1   7 use Net::DNS::Check::Host;
  1         1  
  1         18  
14 1     1   616 use Net::DNS::Check::NSQuery;
  1         3  
  1         25  
15 1     1   523 use Net::DNS::Check::Test;
  1         2  
  1         24  
16 1     1   1133 use Data::Dumper;
  1         9707  
  1         2925  
17              
18              
19             my %PUBLIC_ARGS = map { $_ => 1 } qw(
20             config
21             config_file
22             domain
23             nserver debug);
24              
25              
26             sub new {
27 0     0 1   my ($class) = shift;
28              
29 0           my $self = {};
30              
31 0           bless $self, $class;
32              
33             # Hash ref of auth nameservers # {$nsname}->{ip} array ref of found ip address # {$nsname}->{ip_orig} array ref of decoded ip address from nserver param
34             # {$nsname}->{host} host object created
35             # {$nsname}->{status} status information about nserver.. it can contains
36             # the error from host object or the error from nsquery object
37 0           $self->{nsauth} = {};
38              
39             # Contains the summary of test:
40             # Ex:
41             # OK 10
42             # E 3
43             # W 1
44             # $self->{test_summary} = {};
45              
46             # Contains the object ref of executed tests
47 0           $self->{test_obj} = {};
48              
49             # Array Ref that contains the NSQuery objects
50 0           $self->{nsquery} = [];
51              
52              
53             # Default true
54 0           $self->{check_status} = 1;
55              
56             # Process arguments. Return false if not mandatory arguments exist
57 0 0         unless ($self->_process_args(@_)) {
58 0           croak("\nnserver param not found!\n");
59             }
60              
61              
62              
63             # General HostsList: contains the Host object of
64             # hosts outside of the domain name
65 0           $self->{hostslist} = new Net::DNS::Check::HostsList(
66             domain => $self->{domain},
67             config => $self->{config}
68             );
69            
70              
71             # Decode $self->{nserver} string and create $self->{nsauth} hash ref;
72             # If there isn't a nserver string we try to find ns from recursion
73 0 0         if ( $self->{nserver} ) {
74             # Decode nserver string
75 0           $self->{nsauth} = $self->_decode_ns($self->{nserver});
76             } else {
77             # Search for ns record
78 0           $self->{nsauth} = $self->_auth_finder();
79             }
80              
81             # authoritative nameservers check (at least one must exists)
82 0 0         unless ( keys %{$self->{nsauth}} ) {
  0            
83 0           $self->{error} = 'NXDOMAIN';
84 0           $self->{check_status} = 0;
85             }
86              
87             # if we haven't any error we proceed with _ns_query
88 0 0         unless ( $self->{error} ) {
89 0           $self->_ns_query();
90             }
91              
92 0           return $self;
93             }
94              
95              
96              
97             # Process input arguments. Only %PUBLIC_ARGS keys are
98             # accepted and copy valid arguments to $self hash/object
99             sub _process_args {
100 0     0     my ($self, %args) = @_;
101              
102 0           foreach my $attr ( keys %args) {
103 0 0         next unless $PUBLIC_ARGS{$attr};
104            
105 0           $self->{$attr} = $args{$attr};
106             }
107              
108             # Create a default config object if no one is passed
109 0   0       $self->{config} ||= new Net::DNS::Check::Config();
110              
111              
112             # Load a configuration from a config file.
113             # The config file override the default params contained
114             # in Config object or Config object passed.
115 0 0         if ( $self->{config_file} ) {
116             # Not yet implemented
117             # $self->{config}->load_conf_file();
118             }
119              
120             # If there is not a debug param we get it from Config
121 0 0         unless (defined $self->{debug} ) {
122 0           $self->{debug} = $self->{config}->debug_default();
123             }
124              
125 0           $self->{domain} = lc $self->{domain};
126 0           $self->{qdomain} = $self->{domain};
127 0           $self->{qdomain} =~ s/\./\\./g;
128              
129             # Return the mandatory arguments
130 0           return $self->{domain};
131             }
132              
133              
134              
135             # Decode nsstring and transform it to hash ref
136             # Example:
137             # "dns.foo.com=10.10.10.2,192.168.1.2;dns2.foo.com=10.11.2.2"
138             # Created HASH:
139             # dns.foo.com => [ 10.10.10.2, 192.168.1.2 ],
140             # dns2.foo.com => [ 10.11.2.2 ]
141             sub _decode_ns() {
142 0     0     my $self = shift;
143 0           my $nsstr = lc shift;
144              
145 0           my %nshash;
146              
147             # We need a regexp check of $nsstr
148 0 0         if ($nsstr) {
149 0           my @nsarray = split(';', $nsstr);
150 0           foreach my $ns ( @nsarray ) {
151 0           my ($nsname, $nsip) = split('=',$ns);
152 0           my @ip;
153              
154 0 0         if ( $nsname ) {
155 0 0         if ( $nsip ) {
156 0           @ip = split(',',$nsip);
157             }
158              
159 0           $nshash{$nsname}->{ip_orig} = [ @ip ];
160              
161 0           my $host;
162 0 0         if ( $nsname =~ /^(.*\.$self->{qdomain}|$self->{qdomain})$/ ) {
163 0           $host = new Net::DNS::Check::Host(
164             debug => $self->{debug},
165             host => $nsname,
166             config => $self->{config},
167             ip => [ @ip ],
168             ip_orig => [ @ip ]
169             );
170              
171             } else {
172 0           $host = $self->{hostslist}->add_host( hostname => $nsname, ip => [ @ip ], ip_orig => [ @ip ] );
173             }
174              
175 0           $nshash{$nsname}->{ip} = $host->get_ip();
176 0           $nshash{$nsname}->{status} = $host->error();
177 0           $nshash{$nsname}->{host} = $host;
178             }
179             }
180             }
181            
182 0           return \%nshash;
183             }
184              
185              
186             # FIXED? PRENDENDO l'authority section a volte prendiamo i root ns se
187             # ad esempio il nameserver a cui poniamo lo damanda non e' autoritativo
188             # per la zona
189             ###################################
190             # This function try to find the dns servers of a domain.
191             # This function doesn't use any local resolver but starts
192             # query using Net::DNS::Resolver::Recurse facility.
193             # The main goal of the function is to find delagated nameservers
194             # (some time delegated are not the same of authoritative na) of a domain
195             # asking them to auth ns of the upper domain.
196             # For example if I need to find the auth nservers of foo.com domain
197             # I ask them to .com auth nservers and not to foo.com nserver.
198             # We found different implementation in the answer from bind8
199             # to bind9. BIND8 answers with the information contained in
200             # the delegated zones, while bind9 returns the reference
201             # to auth nservers of the zone and so I get the answer from them
202             # (Authority section).
203             # For example: if I ask for the NS records for foo.com
204             # I got the answer directly from auth nservers of .com because
205             # .com auth nservers (Root NS) use BIND8, but if I ask for
206             # foo.it I get the ns list from auth ns of foo.it and
207             # not from .it auth ns because the majority of them use BIND9.
208             # This function so try to get the answer always from delegating
209             # nservers.
210             sub _auth_finder() {
211 0     0     my $self = shift;
212              
213 0           my @split = split('\.', $self->{domain});
214 0           shift(@split);
215 0           my $parent = join ('.', @split );
216 0           my @ns;
217             my $packet;
218 0           my %nshash;
219              
220 0 0         if ($self->{debug} > 0) {
221 0           print <
222              
223             Searching for delegated nameservers of $self->{domain}
224             ============================================
225             DEBUG
226             }
227              
228              
229             # We create an object for Resolver
230 0           my $resolver = Net::DNS::Resolver->new(
231             recurse => 0,
232             debug => ($self->{debug} > 2),
233             retrans => $self->{config}->query_retrans(),
234             retry => $self->{config}->query_retry(),
235             tcp_timeout => $self->{config}->query_tcp_timeout(),
236             );
237              
238              
239             # We create an object for Resolver Recurse
240 0           my $recurse = Net::DNS::Resolver::Recurse->new(
241             debug => ($self->{debug} > 2),
242             retrans => $self->{config}->query_retrans(),
243             retry => $self->{config}->query_retry(),
244             tcp_timeout => $self->{config}->query_tcp_timeout(),
245             );
246 0           $recurse->hints( @{$self->{config}->rootservers()} );
  0            
247              
248             # We ask for NS records of parent domain
249 0           $packet = $recurse->query_dorecursion( $parent , "NS");
250              
251 0 0         if ($self->{debug} > 0) {
252 0           print <
253              
254             Looking for authoritative nameservers of parent domain: $parent
255             DEBUG
256             }
257              
258              
259 0 0         if ($packet) {
260 0           foreach my $rr ( $packet->answer ) {
261 0 0         if ($rr->type eq 'NS') {
262 0 0         push(@ns, $rr->nsdname()) if ($rr->nsdname);
263 0 0         if ($self->{debug} > 0) {
264 0           my $ns = $rr->nsdname();
265 0           print <
266             $parent NS $ns
267             DEBUG
268             }
269             }
270             }
271             } else {
272             # No answer from root nameserver.... link problem
273 0           $self->{error} = 'NOANSWER';
274 0           return {};
275             }
276              
277             # Unresolvable domain $parent and then $self->{domain}
278 0 0         unless (@ns) {
279 0           $self->{error} = 'NXDOMAIN';
280 0           return {};
281             }
282              
283              
284             # We are looking for $self->{domain} delegated ns list (querying the authoritative
285             # nameservers of father of $self->{domain})
286             # We stop to the first answer found
287 0           foreach my $qns ( @ns ) {
288 0           my $address;
289            
290             # Try to get the address of auth nameservers of father domain
291 0           $packet = $recurse->query_dorecursion( $qns , "A");
292 0 0         if ($self->{debug} > 0) {
293 0           print <
294              
295             Looking for A RR of $qns
296             DEBUG
297             }
298              
299              
300            
301 0 0         if ($packet) {
302 0           foreach my $rr ( $packet->answer ) {
303 0 0         if ($rr->type eq 'A') {
304 0           $address = $rr->address;
305 0 0         if ($self->{debug} > 0) {
306 0           my $ip = $rr->address();
307 0           print <
308             $qns A $ip
309             DEBUG
310             }
311             }
312             }
313             }
314              
315            
316             # if we found an address we try to query it, otherwise we look for another dns
317 0 0         if ($address) {
318 0           $resolver->nameservers( ( $address ) );
319 0           $packet = $resolver->send( $self->{domain}, "NS");
320 0 0         if ($self->{debug} > 0) {
321 0           print <
322              
323             Query $address for NS RR of $self->{domain}
324             DEBUG
325             }
326              
327             # If we haven't an answer we try with another dns
328 0 0         if ($packet) {
329 0           my @nsresult;
330              
331 0 0         if ( $packet->answer() ) {
332 0           @nsresult = grep { $_->type eq 'NS' } $packet->answer();
  0            
333             } else {
334 0           foreach my $rr ( $packet->authority() ) {
335             # We consider valid only authority information
336             # about the domain we are looking for:
337             # sometime we got authority section with root nameservers
338             # and usually is not the answer we want (lame delegation).
339             # If one $rr->name is equal to $parent probably all
340             # name are equal to parent... anyway we check all of them
341 0 0 0       if ( lc($rr->name) eq lc($self->{domain}) and $rr->type eq 'NS' ) {
342 0           push(@nsresult, $rr);
343             }
344             }
345             #@nsresult = grep { $_->type eq 'NS' } $packet->authority();
346             }
347              
348              
349 0 0         if (@nsresult) {
350             # Splitted in two foreach loop a better debug output
351              
352             # We get all NS RR for every nameservers found,
353             # we add them to nshash and, at present, we add them to
354             # general hostslist. Note: not all hosts should be added
355             # to general hostslist
356 0           foreach my $rr ( @nsresult ) {
357 0           my $nsname = lc $rr->nsdname();
358 0 0         if ($nsname) {
359 0 0         if ($self->{debug} > 0) {
360 0           print " NS Found $nsname\n";
361             }
362 0           $nshash{$nsname}->{ip_orig} = [];
363             }
364             }
365            
366 0 0         if ($self->{debug} > 0 ) {
367 0           print <
368              
369             Searching for IP of delegated nameservers of $self->{domain}
370             ============================================
371             DEBUG
372             }
373              
374 0           foreach my $nsname ( keys %nshash ) {
375              
376 0           my $host;
377 0 0         if ( $nsname =~ /^(.*\.$self->{qdomain}|$self->{qdomain})$/ ) {
378 0           $host = new Net::DNS::Check::Host(
379             debug => $self->{debug},
380             host => $nsname,
381             config => $self->{config},
382             );
383             } else {
384 0           $host = $self->{hostslist}->add_host( hostname => $nsname );
385             }
386              
387 0           $nshash{$nsname}->{ip} = $host->get_ip();
388 0           $nshash{$nsname}->{status} = $host->error();
389 0           $nshash{$nsname}->{host} = $host;
390             }
391              
392 0           last;
393             } else {
394 0 0         if ($self->{debug} > 0) {
395 0           print <
396             Not Authoritative answer
397             DEBUG
398             }
399              
400             }
401             } else {
402 0 0         if ($self->{debug} > 0) {
403 0           print <
404             No answer: time out
405             DEBUG
406             }
407             }
408             }
409             }
410              
411 0           return \%nshash;
412             }
413              
414              
415             # Create NSQuery object, one for every auth nameservers
416             sub _ns_query {
417 0     0     my $self = shift;
418              
419             #print Dumper $self->{nsauth};
420 0           foreach my $nsname ( keys %{ $self->{nsauth} } ) {
  0            
421              
422             # next if ($nsname eq 'dns3.nic.it' || $nsname eq 'dns2.nic.it' );
423              
424             # If we have the IP address
425 0 0         if ( scalar @{$self->{nsauth}->{$nsname}->{ip}} > 0 ) {
  0            
426              
427 0           my $queryobj = new Net::DNS::Check::NSQuery(
428             config => $self->{config},
429             domain => $self->{domain},
430             nserver => $nsname,
431             ip => $self->{nsauth}->{$nsname}->{ip},
432             hostslist => $self->{hostslist}
433             );
434              
435             # If there is an error in Net::DNS::Check::NSQuery
436 0 0         unless ( $queryobj->error() ) {
437 0           push(@{$self->{nsquery}}, $queryobj);
  0            
438             } else {
439 0           $self->{check_status} = 0;
440 0           $self->{nsauth}->{$nsname}->{status} = $queryobj->error();
441 0 0         if ($self->{debug} > 0 ) {
442 0           my $error = $queryobj->error();
443 0           print <
444             Error: $error
445             DEBUG
446             }
447             }
448             } else {
449 0 0         if ($self->{debug} > 0 ) {
450 0           my $error;
451 0 0         if ( $self->{nsauth}->{$nsname}->{host} ) {
452 0           $error = $self->{nsauth}->{$nsname}->{host}->error();
453             }
454              
455 0           print <
456              
457             Query for RR ANY for $self->{domain} to $nsname
458             =======================================================
459             $nsname IP: not found
460             Error: $error
461              
462             SKIP
463             DEBUG
464             }
465              
466 0           $self->{check_status} = 0;
467             }
468             }
469              
470             }
471              
472              
473             sub check {
474 0     0 1   my $self = shift;
475              
476 0           my $result;
477              
478             # Return and set check_status to false if nsquery array is empty
479 0 0         unless ( @{$self->{nsquery}} ) {
  0            
480 0           $self->{check_status} = 0;
481              
482 0           return;
483             }
484              
485              
486 0           foreach my $test_name ( keys %{ $self->{config}->test_configured() } ) {
  0            
487 0           my $test = new Net::DNS::Check::Test(
488             type => $test_name,
489             nsquery => $self->{nsquery},
490 0           nsauth => [ keys %{$self->{nsauth}} ],
491             config => $self->{config},
492             hostslist => $self->{hostslist}
493             );
494              
495              
496 0           $self->{test_obj}->{$test_name} = $test;
497              
498             # If test_status is true or in other word the test doesn't fail
499 0 0         if ( $test->test_status() ) {
500 0           $self->{test_obj}->{$test_name}->{status} = $self->{config}->ok_status();
501 0           $self->{test_summary}->{$self->{config}->ok_status()}++;
502             } else {
503              
504 0   0       my $status = $self->{config}->test_conf( test => $test_name ) || $self->{config}->default_status();;
505              
506 0           $self->{test_obj}->{$test_name}->{status} = $status;
507 0           $self->{test_summary}->{$status}++;
508              
509 0 0         if ( grep { $_ eq $status } @{$self->{config}->error_status()} ) {
  0            
  0            
510 0           $self->{check_status} = 0;
511             }
512             }
513             }
514              
515 0           return $self->{check_status};
516             }
517              
518              
519             # Returns the list of executed tests or the list of executed test in a specific status
520             sub test_list() {
521 0     0 1   my $self = shift;
522 0           my $status = shift;
523              
524 0 0 0       unless ( defined $self->{test_obj} || defined $self->{config}->{$status} ) {
525 0           return;
526             }
527              
528 0 0         if ($status) {
529 0           my @status_array;
530 0           foreach my $test_name (keys %{$self->{test_obj}}) {
  0            
531 0 0         if ($self->{test_obj}->{$test_name}->{status} eq $status) {
532 0           push(@status_array, $test_name);
533             }
534             }
535 0           return @status_array;
536             } else {
537 0           return keys %{$self->{test_obj}};
  0            
538             }
539             }
540              
541             # Returns the status of $test_name test
542             sub test_status() {
543 0     0 1   my $self = shift;
544 0           my $test_name = shift;
545              
546 0 0         unless ( $test_name ) {
547 0           return;
548             }
549              
550 0           return $self->{test_obj}->{$test_name}->{status};
551             }
552              
553             # Returns the Net::DNS::Check::Test object of $test_name test
554             sub test_object() {
555 0     0 1   my $self = shift;
556 0           my $test_name = shift;
557              
558 0 0         unless ( $test_name ) {
559 0           return;
560             }
561              
562 0           return $self->{test_obj}->{$test_name};
563             }
564              
565             # Returns the result of Net::DNS::Check::Test::test_detail() for $test_name test
566             sub test_detail() {
567 0     0 1   my $self = shift;
568 0           my $test_name = shift;
569              
570 0 0         return unless $test_name;
571              
572 0 0 0       unless ( $test_name or $self->{test_obj}->{$test_name} ) {
573 0           return;
574             }
575              
576 0           return $self->{test_obj}->{$test_name}->test_detail();
577             }
578              
579              
580              
581              
582              
583             # Return the number of executed test of the requested status.
584             # For Example: if $status = OK the function return the number of ok tests
585             # If no status = '' returns an hash containing the number of all result for
586             # every level ( OK => 5, E => 1, F => 0, W => 0);
587             sub test_summary() {
588 0     0 1   my $self = shift;
589 0           my $status = shift;
590              
591 0 0         unless ( defined $self->{test_summary} ) {
592 0           return {};
593             }
594              
595 0 0         if ( $status ) {
596 0           return $self->{test_summary}->{$status};
597             } else {
598 0           return $self->{test_summary};
599             }
600             }
601              
602              
603             # Used to knows if the global process of checking the dns of the domain
604             # is ok or not
605             # It Returns true if check_status is true and if there is an error returns
606             # an empty value
607             sub check_status() {
608 0     0 1   my $self = shift;
609              
610 0   0       return ( $self->{check_status} && not $self->{error} );
611             }
612              
613              
614             # Returns the array of authoritative/delegated nameserver
615             sub nsauth() {
616 0     0 1   my $self = shift;
617 0           my $nsname = shift;
618              
619 0           return keys %{$self->{nsauth}};
  0            
620             }
621              
622              
623             # This function returns status information about a nameserver
624             sub ns_status() {
625 0     0 1   my $self = shift;
626 0           my $nsname = shift;
627              
628 0 0         return unless ( $nsname );
629              
630 0           return $self->{nsauth}->{$nsname}->{status};
631             }
632              
633              
634             # Return the domain checked or we want to check
635             sub domain() {
636 0     0 1   my $self = shift;
637              
638 0           return $self->{domain};
639             }
640              
641              
642             sub error() {
643 0     0 1   my $self = shift;
644              
645 0           return $self->{error};
646             }
647              
648              
649             1;
650              
651             __END__