[ Team LiB ] Previous Section Next Section

Recipe 18.1 Simple DNS Lookups

18.1.1 Problem

You want to find the IP address of a host or turn an IP address into a name. Network servers do this to authenticate their clients, and clients do it when the user gives them a hostname. But Perl's socket library requires an IP address. Furthermore, many servers produce log files containing IP addresses, but hostnames are more useful to analysis software and humans.

18.1.2 Solution

If you have a name like www.perl.com, use gethostbyname if you want all addresses:

use Socket;

@addresses = gethostbyname($name)   or die "Can't resolve $name: $!\n";
@addresses = map { inet_ntoa($_) } @addresses[4 .. $#addresses];
# @addresses is a list of IP addresses ("208.201.239.48", "208.201.239.49")

Or use inet_aton if you need only the first address:

use Socket;

$address = inet_ntoa(inet_aton($name));
# $address is a single IP address "208.201.239.48"

If you have an IP address like "208.201.239.48", use:

use Socket;

$name = gethostbyaddr(inet_aton($address), AF_INET)
            or die "Can't resolve $address: $!\n";
# $name is the hostname ("www.perl.com")

18.1.3 Discussion

This process is complicated because the functions are mere wrappers for C system calls, so you have to convert IP addresses from ASCII strings ("208.146.240.1") into C structures. The standard Socket module provides inet_aton to convert from ASCII to the packed numeric format and inet_ntoa to convert back:

use Socket;
$packed_address = inet_aton("208.146.140.1");
$ascii_address  = inet_ntoa($packed_address);

The gethostbyname function takes a string containing the hostname (or IP address). In scalar context, it returns the remote IP address suitable for passing to inet_ntoa (or undef on error). In list context, it returns a list of at least five elements (or an empty list on error). The returned list is:

Index

Meaning

0

Official name of host

1

Aliases (space-separated string)

2

Address type (normally AF_INET)

3

Length of address structure (irrelevant)

4,5, ...

Address structures

A hostname may have more than one address, as often seen for busy web sites where many machines serve identical web pages to share the load. In such situations, the DNS server that provides addresses rotates them to balance the load. If you need to pick an IP address to connect to, just select the first. But if it doesn't work, try the rest as well.

$packed = gethostbyname($hostname)
            or die "Couldn't resolve address for $hostname: $!\n";
$address = inet_ntoa($packed);
print "I will use $address as the address for $hostname\n";

If you're using hostnames to permit or deny access to a service, be careful. Anyone can set their DNS server to identify their machine as www.whitehouse.gov, www.yahoo.com, or this.is.not.funny. You can't know whether the machine really has the name it claims to have until you use gethostbyname and check that the original address is in the address list for the name.

# $address is the IP address I'm checking, like "128.138.243.20"
use Socket;
$name    = gethostbyaddr(inet_aton($address), AF_INET)
                or die "Can't look up $address : $!\n";
@addr    = gethostbyname($name)
                or die "Can't look up $name : $!\n";
$found   = grep { $address eq inet_ntoa($_) } @addr[4..$#addr];

It turns out that even with this algorithm, you can't be absolutely sure of the name due to a variety of mechanisms that can circumvent this technique. Even the IP address from which packets appear to be coming can be spoofed, so you should never rely on the network layer for authentication. Always do authentication yourself (with passwords or cryptographic challenges) when it matters, because the IPv4 network was not designed to provide security.

More information is kept about a host than just addresses and aliases. To access this information, use the Net::DNS module from CPAN. For instance, Example 18-1 shows how to retrieve the MX (mail exchange) records for an arbitrary host.

Example 18-1. mxhost
  #!/usr/bin/perl -w
  # mxhost - find mx exchangers for a host
  use Net::DNS;
  use strict;
  my ($host, $res, @mx);  
  $host = shift or die "usage: $0 hostname\n";
  $res = Net::DNS::Resolver->new( );
  @mx = mx($res, $host)
     or die "Can't find MX records for $host (".$res->errorstring.")\n";
  foreach my $record (@mx) {
      print $record->preference, " ", $record->exchange, "\n";
  }

Here's some output:

% mxhost cnn.com
10 atlmail1.turner.com
10 atlmail4.turner.com
20 atlmail2.turner.com
30 nymail1.turner.com

The inet_aton function takes a string containing a hostname or IP address, as does gethostbyname, but it returns only the first IP address for the host. To find them all, you'll need to add more code. The Net::hostent module provides by-name access for that; Example 18-2 shows an example of its use.

Example 18-2. hostaddrs
  #!/usr/bin/perl -w
  # hostaddrs - canonize name and show addresses
  use Socket;
  use Net::hostent;
  use strict;
  my ($name, $hent, @addresses);
  $name = shift || die "usage: $0 hostname\n";
  if ($hent = gethostbyname($name)) {
         $name      = $hent->name;                # in case different
      my $addr_ref  = $hent->addr_list;
         @addresses = map { inet_ntoa($_) } @$addr_ref;
  }
  print "$name => @addresses\n";

Here's the output:

% hostaddrs www.oreilly.com
www.oreilly.com => 208.201.239.37 208.201.239.36

% hostaddrs www.whitehouse.gov
a1289.g.akamai.net => 216.241.36.232 216.241.36.230

18.1.4 See Also

The gethostbyname and gethostbyaddr functions in Chapter 29 of Programming Perl and in perlfunc(1); the documentation for the Net::DNS module from CPAN; the documentation for the standard Socket and Net::hostent modules

    [ Team LiB ] Previous Section Next Section