Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend check_dns.pl so it can take a list of domains #338

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 108 additions & 97 deletions check_dns.pl
Original file line number Diff line number Diff line change
Expand Up @@ -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)" ],
Expand All @@ -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";
Expand All @@ -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();
Expand All @@ -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 ";
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

multi-line will break some monitoring servers output / dashboards as traditionally only 1 line is guaranteed to be displayed, you wouldn't want the output on some systems to be "DNS OK:" and nothing else...

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;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will mess up in --verbose mode

}
@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'";
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

multi-line will break some monitoring servers output

$msg .= $perfdata;

my $extended_command = dirname $progname;
Expand Down