A Smart Email Testing Utility
— or —
Performing DNS Queries with Perl

  • Presented by Barry Brevik

Reference Materials

Some Background Information

A core part of a DNS server is the zone file, which is an ASCII text file, and typically represents a single domain. The zone file defines the domain, and the DNS API extracts records from a DNS server's zone file. The zone file contains multiple record types that describe the authority of the domain, define addresses of name servers, and others. Windows Server DNS is based on the same text file format. Zone files are located as follows:

Operating System
Location
Forward Lookup Zone
Reverse Lookup Zone
unix
/var/named/
db.obedience4life.com
db.12.176.203
Windows
%systemroot%\system32\dns\
obedience4life.dns
203.176.12.in-addr.arpa.dns

 

Shown below is a typical zone file. Each record in the file is referred to as a 'Resource Record'. The nameserver records have been highlighted:


obedience4life.com. 10800 IN SOA obedience4life.com. root.obedience4life.com. (
                          20110129 ; Serial
                          10800    ; Refresh
                          3600     ; Retry
                          604800   ; Expire
                          3600 )   ; Minimum TTL

obedience4life.com.       10800 IN TXT "v=spf1 a mx -all"
obedience4life.com.       10800 IN A 12.176.203.134
obedience4life.com.       10800 IN MX 100 INBOUND.OBEDIENCE4LIFE.COM.NETSOLMAIL.NET.
obedience4life.com.       10800 IN NS ns1.glacieraudio.com.
obedience4life.com.       10800 IN NS ns1.mvllc.net.
obedience4life.com.       10800 IN NS ns1.otlamp.com.
ftp.obedience4life.com.   10800 IN A 12.176.203.134
MAIL.obedience4life.com.  10800 IN CNAME MAIL.OBEDIENCE4LIFE.COM.NETSOLMAIL.NET.
SMTP.obedience4life.com.  10800 IN CNAME SMTP.OBEDIENCE4LIFE.COM.NETSOLMAIL.NET.
www.obedience4life.com.   10800 IN A 12.176.203.134
      

Basics of Using Net::Dns with Perl

use Net::DNS;  # Place this directive at the top of your code.

 

# Use the constructor to create a RESOLVER object. Your program can have

# any number of resolvers simultaneously, each keeping it's own state.

my $resolver = Net::DNS::Resolver -> new;

 

# Resolver queries return DNS PACKET objects. PACKET objects are constructed
# as a HASH of ARRAYs, with the following 5 hash keys:

 

# header     => An array of HEADER objects.

 

# question   => An array of QUESTION objects.

 

# answer     => An array of RESOURCE RECORD objects. We will use this data item.

 

# authority  => An array of RESOURCE RECORD objects.

 

# additional => An array of RESOURCE RECORD objects.

 

# There are 3 query methods available (examples below):

 

#

 

search - Uses the 'search list', which is a list of domains to be searched

#          and which might be in /etc/resolv.conf, or in the ENVIRONMENT.

#          On Windows machines it looks for a search list in the REGISTRY.

query  - Does not use the search list. If the query name does not have any

#          dots, and defnames is true, it appends the default domain.

send   - Neither the searchlist nor the default domain will be used.

 

my $packet = $resolver -> search($domain);

 

# We use this when extracting Name Server records.

my $packet = $resolver -> query($domain, 'NS');

 

# We use this when extracting Start Of Authority (SOA) records.

my $packet = $resolver -> send($domain, 'SOA', 'IN');

 

A Stripped Down Working DNS Example

# The following code is functional, but is intended only for demonstration.

# The code is radically over simplified, and there is no error checking. In

# other words, do not use this code as-is.

 

use strict;

use warnings;

use Net::DNS;


my $domain      = 'obedience4life.com';

my $resolver    = Net::DNS::Resolver -> new;

my $packet      = $resolver->query($domain, 'NS');

my @nameServers = $packet   ->answer;


foreach my $rrNameServer (@nameServers)

{

  # We have specifically asked for NS records, but the documentation warn

  # us that the resolver may return other types of records aside from the NS

  # records, so it is important to check each record type.

  next unless $rrNameServer->type eq 'NS';

  print $rrNameServer       ->string;

}

# This is the output from the above program:

obedience4life.com.     9659    IN      NS      ns1.mvllc.net.
obedience4life.com.     9659    IN      NS      ns1.glacieraudio.com.
obedience4life.com.     9659    IN      NS      ns1.otlamp.com.
        

A Partial List of Additional Net::Dns Methods

# Gets or sets the re-transmission interval (in seconds). Default is 5 seconds.

$resolver -> retrans(3);

# Gets or sets the number of times to retry a query. Default is 4.

$resolver -> retry(2);

# Gets or sets the UDP timeout (in seconds). Default is undef which causes

# (retrans * retry) to be used.

$resolver -> udp_timeout(16);

# When TRUE, calls to search, query, and send will print debug info to <STDOUT>.

$resolver -> debug(1);

# Returns a string containing the most recent error message.

my $error = $resolver -> errorstring;

# The resolver will use IPv6 if the libraries are available and if the target

# nameserver is using IPv6. We can force the resolver to use IPv4 like this:

$resolver -> force_v4(1);

# We can use TCP instead of UDP. This is good if our connection is flaky, or

# if answers are too big for a UDP packet. I use UDP as default, and TCP as a

# run-time option to avoid the overhead of open/close of TCP connections.

$resolver -> usevc(1);

# Gets or sets the UDP packet size. Default is the default DNS packet size.

$resolver -> udppacketsize(2048);

# There is an entire subset of methods that perform background operations.

# The resolver can be queried to determine if the operation is complete.

A Skeletal Example of an Email Transfer

 

# The following code is an outbound email transfer. We will put this together

# with DNS queries in the actual utility. Note that there is no error checking!

 

use strict;

use warnings;

use Socket;


my $mailhost = < MX host IP address expressed in binary >;

my $protocol = (getprotobyname('tcp'))[2];

my $mailport = (getservbyname('smtp', 'tcp'))[2];

 

# Open a TCP socket and name it 'MAIL'.

socket(MAIL, AF_INET, SOCK_STREAM, $protocol);

select((select(MAIL), $| = 1)[0]);  # Unbuffer the socket.

 

# Connect the MAIL socket to the target mail server. S n a4 x8 (below) means:

# S = unsigned short (the protocol), n = a short in network order (the port

# number), a4 = 4 byte ASCII string (packed address), x8 = 8 null bytes.

connect(MAIL, pack('Sna4x8', AF_INET, $mailport, $mailhost));

 

print MAIL "helo $domain\r\n"; # Must use \r\n instead of \n

print MAIL "mail from <$from>\r\n"; # when programming sockets.

print MAIL "rcpt to <$target-user>\r\n";

print MAIL "< your message content goes here >\r\n";

 

# Send the message text as well as any MIME encoded attachments that you may

# have. When you are finished sending data, print a single dot '.' as the first

# character on an otherwise blank line.

print MAIL ".\r\n";

print MAIL "quit\r\n";  # Close the connection to the target mail server.