Perl Net DNS Tutorial for Querying DNS Servers

Perl is really a very powerful language when it comes to low level system programming requiring intensive processing. Perl Net::DNS library can be used to write powerful web applications involving DNS lookups and queries. Before you start this perl programming you want want to understand certain dns concepts and experiment with dig tool. I would recommend reading this Dig tutorial

In this tutorial i will show you how easy it is to write a DNS diagnostic application involving DNS and resolver. You can also find more information about Net::DNS library in http://net-dns.org

Installing Net DNS

If you have a linux server it is very install to install Net::DNS library either using yum or cpan. You must have perl already installed.

yum install perl-Net-DNS;
cpan -i Net::DNS

Once you have successfully installed create a new test.pl file with permissions 755. Just place the the following 2 lines on top to start working.

#!/usr/bin/perl
use Net::DNS;

then run the perl file

perl test.pl

Get Nameservers and IP with Authoritative Answer

This example code will query the resolver for given name, for its nameservers, its authoritative for the domain and the corresponding IP address. It will fail unless the ‘ancount’ is greater than 0. If ancount is 0 it means no answer.

In order to get the authoritative answer, the script will query each name servers of the for the zone information about the domain. If soa_req->header->aa is set it means authoritative answer else lame delegation. If there are A records missing (means IP address) then glue is incorrectly set.

[sourcecode lang=”sql”]
#!/usr/bin/perl

use Net::DNS;
use Net::IP;
my $rr;

$domain = ‘example.com’;
my $res = Net::DNS::Resolver->new();

######### Nameservers, Authoritative Answer and IP address################

my $ns_req = $res->query($domain, “NS”);
die “No nameservers found for $domain: “, $res->errorstring, “\n”
unless defined($ns_req) and ($ns_req->header->ancount > 0);

$res->recurse(0);

foreach my $nsrr (grep {$_->type eq “NS” } $ns_req->answer) {

my $ns = $nsrr->nsdname;

push(@nservers,$ns);

my $local_res = Net::DNS::Resolver->new();

my $a_req = $local_res->query($ns, ‘A’);

unless ($a_req) {
warn “A record fail $ns (no glue): “, $res->errorstring, “\n”;
next;
}

foreach my $ip (map { $_->address } grep { $_->type eq ‘A’ } $a_req->answer) {

$res->nameservers($ip);

print “$ns ($ip)”;
push (@nsips,$ip);

my $soa_req = $res->send($domain, ‘SOA’, ‘IN’);

unless (defined($soa_req)) {
warn “SOA fail for $ns:”,$res->errorstring, “\n”;
next;
}

# Check Authoritative Answer

unless ($soa_req->header->aa) {
warn “Nameserver NOT authoritative or LAME delegation \n”;
next;
} else {
print “[AUTHORITATIVE]\n”;
}

}
}
[/sourcecode]

Checking Parent Nameservers

It is very important to query parent nameservers from root level to see if the nameservers are listed. To start you need a starting root server and then recursively it will query other root servers to obtain nameservers for a particular domain.

[code lang=”sql”]
use Net::DNS::Resolver::Recurse;
my $pres = Net::DNS::Resolver::Recurse->new;
$pres->tcp_timeout(2);
$pres->udp_timeout(2);
$pres->debug(0);

my @root_ns = map $_ . ‘.root-servers.net’, ‘a’..’m’;
$pres->hints(@root_ns);

my $packet = $pres->query_dorecursion($domain, “NS”);
print “Parent Nameservers:\n”;

foreach my $pns ($packet->additional)
{
print $pns->name,”(“, $pns->rdatastr,”)\n”;

}
[/code]

This will output the nameserver NS records that are listed at parent level. You can set debug(1) to see how it works internally.

Check Website IP Address (A Record)

The A record in DNS zone file maps a domain to its corresponding IP address. It is the single most important delegation in a zone file for the domain. Here we query for A record given the domain name.

[code lang=’sql’]
########### WEBSITE A RECORD ###################

my $a_query = $res->search($domain);

print “Website IP Address (A Record)…\n”;

if ($a_query) {
foreach my $rr ($a_query->answer) {
next unless $rr->type eq “A”;
print $rr->address, “\n”;
}
} else {
warn “Unable to obtain A record for your website: “, $res->errorstring, “\n”;
}
[/code]

SOA Record

The SOA record or service of authority contain critical information about TTL, serial and authority nameserver. Here is an example of perl script that can output this information.

[code lang=’sql’]
########################### SOA RECORD ################
print “SOA record\n”;

my $soa_query = $res->query($domain, ‘SOA’);

if ($soa_query) {
($soa_query->answer)[0]->print, “\n”;
}
else {
print “SOA record fail. Result:\n”,$res->errorstring;
}
if ($soa_query) {
print “Serial: “,($soa_query->answer)[0]->serial,”\n”;
print “Refresh: “,($soa_query->answer)[0]->refresh,”\n”;
print “Retry: “,($soa_query->answer)[0]->retry,”\n”;
print “Expire: “,($soa_query->answer)[0]->expire,”\n”;
print “Minimum TTL: “,($soa_query->answer)[0]->minimum,”\n”;

}
[/code]

Checking for Open DNS Server (Recursion)

A nameserver that performs recursive lookups (resolving domain names other than local domains) is known as open dns server. It means anybody from outside your network query your dns server to resolve. Lets say you have example.com and you are asking ns1.example.com to resolve corpocrat.com. If answer comes with A record then it is opendns server.

Refer to this article: https://corpocrat.com…../dig-tool-for-dns-lookups

Put it short, its just like keeping your door open in your home inviting strangers and animals. An open DNS server is a BIG SECURITY RISK and you should disable it and only restrict to your local IPs.

This perl code checks for recursive lookups, if you find answer with A record that means the nameserver is open dns server performing recursive lookups.

[code lang=’sql’]
############# RECURSION CHECK #######################

print “Checking recursion (open dns server)…\n”;
my @nservers = qw(ns1.example.com);

my $rres = Net::DNS::Resolver->new(
nameservers => [@nservers],
recurse => 1, debug => 0,
);

my $r_query = $rres->query($dest,”A”);
my @dest_ips=();

if ($r_query) {
foreach my $rrr ($r_query->answer) {
next unless $rrr->type eq “A”;
print “Recursive lookups are ENABLED in “,$res->nameserver(),”: “,$rrr->address, “\n”;

}
} else {
warn “OK. Recursive queries are not answered. Result: “, $rres->errorstring, “\n”;
}
[/code]

Get Reverse MX Records

If you run a mail server, it is absolutely necessary to setup a reverse IP pointing to your mail server, and if not mails wont get delivered properly mistaking to be spam. By default the IP addresses are owned by hosting companies and if you have to explicitly ask them to do set Reverse IP for your mail server. Most email providers check for reverse IP to prevent spam in email.

This perl code looks for PTR resource in your zone file and retrieves the reverse MX record.

[code lang=’sql’]
####################### Reverse MX Records ########################

print “\nChecking Reverse MX Records …\n”;

my $mx_res = Net::DNS::Resolver->new();

my $mxanswer = $mx_res->query($ip->reverse_ip(),’PTR’);

if($mxanswer) {

foreach my $mrr (grep {$_->type eq “PTR” } $mxanswer->answer) {
print $ip->reverse_ip(),” PTR “,$mrr->ptrdname,”\n”;

} else {
print “No Reverse IP \n”;
}

}
} else {
print “MX records not found\n”;
}
[/code]

SPF Records

SPF is widely used in domain zonefile for preventing spam. It is essential to place a TXT record in your zone file for SPF entry. More information can be found in http://www.openspf.org

This is a simple perl code to detect spf record by querying the dns.

[code lang=’sql’]
############ SPF RECORD #################################

my $spf_query = $res->query($domain,”TXT”);
if ($spf_query) {

foreach $rr ($spf_query->answer) {
#next unless $rr->txtdata eq ‘/v=spf/’;
print “SPF Record Installed OK: “,$rr->txtdata, “\n”;
}
}
else {
warn “Unable to get SPF Record : “, $res->errorstring, “\n”;
}
[/code]

Domain Keys

Domain keys is an authentication framework to digitally sign the email using public keys to prove email originating from partiular domain and not from a phishing or spam site. More information can be found http://domainkeys.sourceforge.net/

It basically contains 2 records (with and without selector) placed under TXT using underscore.

sel_domainkey.example.com IN TXT (with selector)
_domainkey.example.com IN TXT (without selector)

The ‘sel’ is a selector and can be selector name.

The perl code below just tests whether domain key exist or not.

[code lang=’sql’]
############## DOMAIN KEYS ################################

my $dk_query = $res->query(“_domainkey.”.$domain,”TXT”);
if ($dk_query) {

foreach $rr ($dk_query->answer) {
print “Domain Keys installed OK: “,$rr->print,$rr->txtdata, “\n”;
}
}
else {
warn “Unable to obtain Domain Keys: “, $res->errorstring, “\n”;
}
[/code]

Zone Transfer AXFR Check

Zone transfers are done by slave name servers to keep up-to-date information about the zone file from their master server. Allowing the zone transfers or AXFR requests to anybody is a BIG SECURITY RISK as this exposes the entire internal setup of your dns server. You should restrict this only those within the network.

This perl code checks to see if it is possible to perform a zone transfer. To perform a zone transfer of example.com then ask the nameserver ns1.example.com to transfer the zone file for domain example.com. The following example just performs Zone transfer check on only 1 nameserver. If you want to check more nameservers, you have to loop through all NS servers to check for zone transfer.

[code lang=’sql’]
########### ZONE TRANSFER AXFR CHECK ####################
my $zres = Net::DNS::Resolver->new();
$zres->tcp_timeout(5);
$zres->nameservers(‘ns1.example.com’);
my @zone = $zres->axfr($domain);

if (@zone) {
print “ALERT: Zone transfer AXFR is ENABLED.\n”;
# foreach my $rr (@zone) {
# $rr->print;
# }
} else {
print ‘OK. Zone transfer failed: ‘, $zres->errorstring, “\n”;
}
[/code]

Get DNS Server Version

Exposing the version of your dns running on your server, may expose the vulnerabilities if you have older version. This may be a security risk. You will need Net::DNS::Version module to output the version of bind running on the remote server.

[code lang=”python”]
use Net::DNS::Version;
$domain = ‘example.com’;
my $scan = Net::DNS::Version->new({
host => $domain,
timeout => 5
});

my $version = $scan->check;
print “DNS Server Version : $version\n”;
[/code]

Download

I have written a dns test tool written in perl which can perform dns diagnostic test including CNAME, same subnets in CGI format. It is free to download.

Download: dns-test.zip

List of Error codes

The following is a list of error codes returned and their meanings.

NOERROR No error
FORMERR Format error
SERVFAIL Server failure
NXDOMAIN Non-existent domain (name doesn’t exist)
NOTIMP Not implemented
REFUSED Query refused

NOTE: The ‘n’ appears in the print is actually a line break. so include a backward slash before it when using in your scripts..