• No results found

Working with raw network sockets

Chapter 5. TCP/IP Name Services

5.3. Domain Name Service (DNS)

5.3.2. DNS Checking: An Iterative Approach

5.3.2.2. Working with raw network sockets

close(NSLOOK);

}

The benefits of this approach are:

It's a short, quick program to write (perhaps even translated line by line from a real shell script).

We did not have to write any messy network code.

It takes the Unix approach of using a general purpose language to glue together other smaller, specialized programs to get a job done, rather than creating a single monolithic program.

It may be the only approach for times when you can't code the client−server communication in Perl;

for instance, you have to talk with a server that requires a special client and there's no alternative.

The drawbacks of this approach are:

It's dependent on another program outside the script. What if this program is not available? What if this program's output format changes?

It's slower. It has to start up another process each time it wants to make a query. We could have reduced this overhead by opening a two−way pipe to an nslookup process that stays running while we need it. This would take a little more coding skill, but would be the right thing to do if we were going to continue down this path and further enhance this code.

You have less control. We are at the external program's mercy for implementation details. For instance, here nslookup (more specifically the resolver library nslookup is using) is handling server timeouts, query retries, and appending a domain search list for us.

5.3.2.2. Working with raw network sockets

If you are a "power sysadmin," you may decide calling another program is not acceptable. You might want to implement the DNS queries using nothing but Perl. This entails constructing network packets by hand, shipping them out on the wire, and then parsing the results returned from the server.

5.3.2.2. Working with raw network sockets 182

This is probably the most complicated code you'll find in this entire book, written by looking at the reference sources described below along with several examples of existing networking code (including the module by Michael Fuhr we'll see in the next section). Here is a rough overview of what is going on. Querying a DNS server consists of constructing a specific network packet header and packet contents, sending it to a DNS server, and then receiving and parsing the response from that server.[5]

[5]For the nitty−gritty details, I highly recommend you open RFC1035 to the section entitled

"Messages" and read along.

Each and every DNS packet (of the sort we are interested in) can have up to five distinct sections:

Header

Contains flags and counters pertaining to the query or answer (always present).

Question

Contains the question being asked of the server (present for a query and echoed in a response).

Answer

Contains all the data for the answer to a DNS query (present in a DNS response packet).

Authority

Contains information on where an authoritative response may be retrieved.

Additional

Contains any information the server wishes to return in addition to the direct answer to a query.

Our program will concern itself strictly with the first three of these. We'll be using a set of pack( ) commands to create the necessary data structure for a DNS packet header and packet contents. We pass this data structure to the IO::Socket module that handles sending this data out as a packet. The same module will also listen for a response on our behalf and return data for us to parse (using unpack( )).

Conceptually, this process is not very difficult.

There's one twist to this process that should be noted before we look at the code. RFC1035 (Section 4.1.4) defines two ways of representing domain names in DNS packets: uncompressed and compressed. The uncompressed representation places the full domain name (for example, host.oog.org) in the packet, and is nothing special. But, if the same domain name is found more than once in a packet, it is likely a compressed representation will be used for everything but the first mention. A compressed representation replaces the domain information or part of it with a two−byte pointer back to the first uncompressed representation. This allows a packet to mention host1, host2, and host3 in longsubdomain.longsubdomain.oog.org, without having to include the bytes for longsubdomain.longsubdomain.oog.org each time. We have to handle both

representations in our code, hence the &decompress routine below. Without further fanfare, here's the code:

use IO::Socket;

$hostname = $ARGV[0];

$defdomain = ".oog.org"; # default domain if not present

@servers = qw(nameserver1 nameserver2 nameserver3); # name of the name servers foreach $server (@servers) {

&lookupaddress($hostname,$server); # populates %results }

%inv = reverse %results; # invert the result hash if (scalar(keys %inv) > 1) { # see how many elements it has print "There is a discrepancy between DNS servers:\n";

use Data::Dumper;

print Data::Dumper−>Dump([\%results],["results"]),"\n";

}

The Five−Minute RCS Tutorial (Perl for System Administration)

5.3.2.2. Working with raw network sockets 183

sub lookupaddress{

# construct the qname section of a packet (domain name in question) for (split(/\./,$hostname)) {

### construct the packet question section ###

$question = pack($lformat."C n2", @labels,

$sock = new IO::Socket::INET(PeerAddr => $server, PeerPort => "domain",

$qr_opcode_aa_tc_rd, $rd_ra,

$qdcount, $ancount, $nscount,

$arcount) = unpack("n C2 n4",$buf);

5.3.2.2. Working with raw network sockets 184

if (!$ancount) { ($position,$qname) = &decompress(12);

($qtype,$qclass)=unpack('@'.$position.'n2',$buf);

# move us forward in the packet to end of question section $position += 4;

###

### unpack all of the resource record sections ###

# handle domain information that is "compressed" as per RFC1035

# we take in the starting position of our packet parse and return

# the name we found (after dealing with the compressed format pointer)

# and the place we left off in the packet at the end of the name we found sub decompress {

Note that this code is not precisely equivalent to that from the previous example because we're not trying to emulate all of the nuances of nslookup's behavior (timeouts, retries, searchlists, etc.). When looking at the three approaches here, be sure to keep a critical eye out for these subtle differences.

The benefits of this approach are:

The Five−Minute RCS Tutorial (Perl for System Administration)

5.3.2.2. Working with raw network sockets 185

It isn't dependent on any other programs. You don't need to know the particulars of another programmer's work.

It may be as fast or faster than calling an external program.

It is easier to tweak the parameters of the situation (timeouts, etc.).

The drawbacks of this approach are:

It's likely to take longer to write and is more complex than the previous approach.

It requires more knowledge external to the direct problem at hand (i.e., you may have to learn how to put DNS packets together by hand, something we did not need to know when we called nslookup).

You may have to handle OS−specific issues yourself (hidden in the previous approach by the work already done by the external program's author).