diff --git a/check_dns.pl b/check_dns.pl index 19b27278f..fd97bca47 100755 --- a/check_dns.pl +++ b/check_dns.pl @@ -47,17 +47,18 @@ BEGIN my $default_type = "A"; my $type = $default_type; my $record; +my @records; my $server; my @servers; my $expected_result; my $expected_regex; -my $expected_regex2; +my $valid_expected_regex; my $no_uniq_results; my $randomize_servers; %options = ( "s|server=s" => [ \$server, "DNS server(s) to query, can be a comma separated list of servers" ], - "r|record=s" => [ \$record, "DNS record to query" ], + "r|record=s" => [ \$record, "DNS record(s) to query, can be a comma separated list of records" ], "q|type=s" => [ \$type, "DNS query type (defaults to '$default_type' record)" ], "e|expected-result=s" => [ \$expected_result, "Expected results, comma separated" ], "R|expected-regex=s" => [ \$expected_regex, "Expected regex to validate against each returned result (anchored, so if testing partial TXT records you may need to use .* before and after the regex, and if differing TXT records are returned then use alternation '|' to support the different regex, see tests/test_dns.sh for an example)" ], @@ -73,16 +74,23 @@ BEGIN for(my $i=0; $i < scalar @servers; $i++){ $servers[$i] = validate_host($servers[$i]); } + grep($type, @valid_types) or usage "unsupported type '$type' given, must be one of: " . join(",", @valid_types); -if($type eq "PTR"){ - $record = isIP($record) or usage "invalid record given for type PTR, should be an IP"; -} elsif($type eq "SRV"){ - $record =~ /^[A-Za-z_\.-]+\.$domain_regex/ or usage "invalid record given for type SRV, must contain only alphanumeric, underscores, dashes followed by a valid domain name format"; -} else { - $record = isDomain($record) or isFqdn($record) or usage "invalid record given, should be a domain or fully qualified host name"; + +$record or usage "record(s) not specified"; +@records = split(/\s*[,\s]\s*/, $record); +for(my $i=0; $i < scalar @records; $i++){ + if($type eq "PTR"){ + $records[$i] = isIP($records[$i]) or usage "invalid record " . $records[$i] . " given for type PTR, should be an IP"; + } elsif($type eq "SRV"){ + $records[$i] =~ /^[A-Za-z_\.-]+\.$domain_regex/ or usage "invalid record" . $records[$i] . " given for type SRV, must contain only alphanumeric, underscores, dashes followed by a valid domain name format"; + } else { + $records[$i] = isDomain($records[$i]) or isFqdn($records[$i]) or usage "invalid record " . $records[$i] . " given, should be a domain or fully qualified host name"; + } } + vlog_option "server", join(",", @servers); -vlog_option "record", $record; +vlog_option "record", join(",", @records); vlog_option "type", $type; if($randomize_servers){ vlog2 "randomizing nameserver list"; @@ -104,7 +112,7 @@ BEGIN } vlog_option "expected results", $expected_result; } -$expected_regex2 = validate_regex($expected_regex) if defined($expected_regex); +$valid_expected_regex = validate_regex($expected_regex) if defined($expected_regex); vlog2; set_timeout(); @@ -128,101 +136,104 @@ BEGIN $res->udp_timeout(2); vlog2 "set resolver timeout to 2 secs per server"; -my @results; -my @rogue_results; -my @missing_results; - -vlog2 "sending query for $record $type record"; -my $start = time; -my $query = $res->query($record, $type); -my $stop = time; -my $total_time = sprintf("%.4f", $stop - $start); -vlog2; -plural @servers; -$query or quit "CRITICAL", "query returned with no answer from server$plural " . join(",", @servers) . " in $total_time secs" . ( $verbose ? " for record '$record' type '$type'" : ""); -vlog2 "query returned in $total_time secs"; -my $perfdata = " | dns_query_time='${total_time}s'"; - -vlog3 "returned records:\n"; -foreach my $rr ($query->answer){ - vlog3 Dumper($rr); - my $result; - if($rr->type eq "A"){ - $result = $rr->address; - } elsif($rr->type eq "CNAME"){ - $result = $rr->cname; - $result =~ s/\.$//; - } elsif($rr->type eq "MX"){ - $result = $rr->exchange; - } elsif($rr->type eq "NS"){ - $result = $rr->nsdname; - } elsif($rr->type eq "PTR"){ - $result = $rr->ptrdname; - } elsif($rr->type eq "SOA"){ - $result = $rr->serial; - } elsif($rr->type eq "SRV"){ - $result = $rr->target; - } elsif($rr->type eq "TXT"){ - $result = $rr->txtdata; - } else { - quit "UNKNOWN", "unknown/unsupported record type '$rr->type' returned for record '$record'"; - } - vlog2 "got result: $result\n"; - if($type eq "A"){ - isIP($result) or quit "CRITICAL", "invalid result '$result' returned for A record by DNS server, expected IP address for A record$perfdata"; - } elsif(grep { $type eq $_ } qw/CNAME MX NS PTR SRV/){ - isFqdn($result) or quit "CRITICAL", "invalid result '$result' returned " . ($verbose ? "for record '$record' type '$type' ": "") . "by DNS server, expected FQDN for this record type$perfdata"; - } elsif($type eq "SOA"){ - isInt($result) or quit "CRITICAL", "invalid serial result '$result' returned for SOA record " . ($verbose ? "'$record' ": "") . "by DNS server, expected an unsigned integer$perfdata"; - } - push(@results, $result); - if(@expected_results){ - unless(grep(lc $_ eq lc $result, @expected_results)){ - vlog3 "result '$result' wasn't found in expected results, added to rogue list"; - push(@rogue_results, $result); +my $start_calls = time; +for(my $i=0; $i < scalar @records; $i++){ + vlog2 "sending query for $records[$i] $type record"; + my @results; + my @rogue_results; + my @missing_results; + my $start_call = time; + my $query = $res->query($records[$i], $type); + my $stop_call = time; + my $call_time = sprintf("%.4f", $stop_call - $start_call); + vlog2; + plural @servers; + $query or quit "CRITICAL", "query returned with no answer from server$plural " . join(",", @servers) . " in $call_time secs" . ( $verbose ? " for record '$records[$i]' type '$type'" : ""); + vlog2 "query returned in $call_time secs"; + my $perfdata = " | dns_query_time='${call_time}s'"; + + vlog3 "returned records:\n"; + foreach my $rr ($query->answer){ + vlog3 Dumper($rr); + my $result; + if($rr->type eq "A"){ + $result = $rr->address; + } elsif($rr->type eq "CNAME"){ + $result = $rr->cname; + $result =~ s/\.$//; + } elsif($rr->type eq "MX"){ + $result = $rr->exchange; + } elsif($rr->type eq "NS"){ + $result = $rr->nsdname; + } elsif($rr->type eq "PTR"){ + $result = $rr->ptrdname; + } elsif($rr->type eq "SOA"){ + $result = $rr->serial; + } elsif($rr->type eq "SRV"){ + $result = $rr->target; + } elsif($rr->type eq "TXT"){ + $result = $rr->txtdata; + } else { + quit "UNKNOWN", "unknown/unsupported record type '$rr->type' returned for record '$records[$i]'"; + } + vlog2 "got result: $result\n"; + if($type eq "A"){ + isIP($result) or quit "CRITICAL", "invalid result '$result' returned for A record by DNS server, expected IP address for A record$perfdata"; + } elsif(grep { $type eq $_ } qw/CNAME MX NS PTR SRV/){ + isFqdn($result) or quit "CRITICAL", "invalid result '$result' returned " . ($verbose ? "for record '$record' type '$type' ": "") . "by DNS server, expected FQDN for this record type$perfdata"; + } elsif($type eq "SOA"){ + isInt($result) or quit "CRITICAL", "invalid serial result '$result' returned for SOA record " . ($verbose ? "'$records[$i]' ": "") . "by DNS server, expected an unsigned integer$perfdata"; + } + push(@results, $result); + if(@expected_results){ + unless(grep(lc $_ eq lc $result, @expected_results)){ + vlog3 "result '$result' wasn't found in expected results, added to rogue list"; + push(@rogue_results, $result); + } } } -} - -@results or quit "CRITICAL", "no result received for '$record' $type record from servers " . join(",", @servers) . " in $total_time secs"; + @results or quit "CRITICAL", "no result received for '$records[$i]' $type record from servers " . join(",", @servers) . " in $call_time secs"; + my @results_uniq = sort(uniq_array(@results)); -my @results_uniq = sort(uniq_array(@results)); -foreach my $expected_result2 (@expected_results){ - unless(grep(lc $_ eq lc $expected_result2, @results_uniq)){ - vlog3 "$expected_result2 wasn't found in results, adding to missing list"; - push(@missing_results, $expected_result2); + foreach my $exp_result (@expected_results){ + unless(grep(lc $_ eq lc $exp_result, @results_uniq)){ + vlog3 "$exp_result wasn't found in results, adding to missing list"; + push(@missing_results, $exp_result); + } } -} -my @regex_mismatches; -if($expected_regex2){ - foreach my $result (@results){ - $result =~ /^$expected_regex2$/ or push(@regex_mismatches, $result); + my @regex_mismatches; + if($valid_expected_regex){ + foreach my $result (@results){ + $result =~ /^$valid_expected_regex$/ or push(@regex_mismatches, $result); + } } + @regex_mismatches = sort(uniq_array(@regex_mismatches)) if(@regex_mismatches); + $msg .= "\n$records[$i] $type record "; + if(scalar @rogue_results or scalar @missing_results){ + critical; + $msg .= "MISMATCH, expected '" . join(",", @expected_results) . "', got '"; + } elsif(scalar @regex_mismatches){ + critical; + $msg .= "regex validation FAILED on '" . join("','", @regex_mismatches) . "' against regex '$expected_regex', returns '"; + } elsif($type eq "SOA"){ + $msg .= "OK return serial '"; + } else { + $msg .= "OK return '"; + } + if ($no_uniq_results){ + $msg .= join("','", @results); + } else { + $msg .= join("','", @results_uniq); + } + $msg .= "'"; + $msg .= " | dns_query_time='${call_time}s'" if $verbose; } -@regex_mismatches = sort(uniq_array(@regex_mismatches)) if(@regex_mismatches); - -$msg .= "$record $type record "; - -if(scalar @rogue_results or scalar @missing_results){ - critical; - $msg .= "mismatch, expected '" . join(",", @expected_results) . "', got '"; -} elsif(scalar @regex_mismatches){ - critical; - $msg .= "regex validation failed on '" . join("','", @regex_mismatches) . "' against regex '$expected_regex', returns '"; -} elsif($type eq "SOA"){ - $msg .= "return serial '"; -} else { - $msg .= "returns '"; -} -if($no_uniq_results){ - $msg .= join("','", @results); -} else { - $msg .= join("','", @results_uniq); -} -$msg .= "'"; -$msg .= " in $total_time secs" if $verbose; + +my $stop_calls = time; +my $total_time = sprintf("%.4f", $stop_calls - $start_calls); +my $perfdata = "\n | total_dns_query_time='${total_time}s'"; $msg .= $perfdata; my $extended_command = dirname $progname;