Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris

mx - Mail Exchanger (MX) information about internet hosts

by grinder (Bishop)
on Oct 08, 2003 at 17:15 UTC ( #297667=sourcecode: print w/replies, xml ) Need Help??
Category: E-Mail Programs
Author/Contact Info /msg me
Description: A tool to find out far more than you ever wanted to know about MX records. In case you didn't know, MX records are a certain type of record used to discover which host will accept SMTP mail for a given domain.
#! /usr/bin/perl -w

use strict;
use Net::DNS;
use Socket qw/inet_aton/;

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

for my $domain( @ARGV ) {
    print "$domain\n" if @ARGV > 1;

    my %res;
    my $rr = $r->query( $domain, 'MX' );
    if( !$rr ) {
        if( $r->errorstring ne 'NOERROR' ) {
            print "\tMX lookup error: ", $r->errorstring, "\n";
        $rr = $r->query( $domain, 'A' );
        if( !$rr ) {
            print "\tA lookup error: ", $r->errorstring, "\n";
        else {
            for my $a( $rr->answer ) {
                   next unless $a->type eq "A";
                my $ptr_rr = $r->query( join( '.', reverse( split /\./
+, $a->address )) . '', 'PTR' );
                if( !$ptr_rr ) {
                    push @{$res{0}}, {
                        ip   => $a->address,
                        forw => $a->address,
                        back => $r->errorstring,
                else {
                    $_->type eq 'PTR' and push @{$res{0}}, {
                        ip   => $a->address,
                        forw => $a->address,
                        back => lc $_->ptrdname,
                    } for( $ptr_rr->answer );
    else {
           for my $mx( $rr->answer ) {
            if( $mx->type eq 'CNAME' ) {
                my $a_rr = $r->query( $mx->cname, 'A' );
                if( !$a_rr ) {
                    push @{$res{-1}}, { ip => $mx->cname, forw => $r->
+errorstring, back => 'CNAME' };
                else {
                    $_->type eq "A"
                        and push @{$res{-1}}, { ip => $mx->cname, forw
+ => $_->address, back => 'CNAME' }
                            for( $a_rr->answer );

            next unless $mx->type eq 'MX';

            my $a_rr = $r->query( $mx->exchange, 'A' );
            if( !$a_rr ) {
                push @{$res{$mx->preference ? $mx->preference : 0}}, {
                    ip   => $mx->exchange,
                    forw => $r->errorstring,
                    back => $r->errorstring,

            my @a;
            for my $a( $a_rr->answer ) {
                next unless $a->type eq "A";
                my $ptr_rr = $r->query( join( '.', reverse( split /\./
+, $a->address )) . '', 'PTR' );
                if( !$ptr_rr ) {
                    push @{$res{$mx->preference}}, {
                        ip => $a->address,
                        forw => $mx->exchange,
                        back => $r->errorstring,
                else {
                    $_->type eq 'PTR' and push @{$res{$mx->preference}
+}, {
                        ip => $a->address,
                        forw => lc $mx->exchange,
                        back => lc $_->ptrdname,
                    } for( $ptr_rr->answer );

    for my $rank( sort { $a <=> $b } keys %res ) {
        for my $host( sort {
            # sort on IP addresses
            (inet_aton($a->{ip}) ? inet_aton($a->{ip}) : $a->{ip}) cmp
            (inet_aton($b->{ip}) ? inet_aton($b->{ip}) : $b->{ip})
            # followed on whether the reverse resolution works
            ($a->{forw} eq $a->{back} ? 0 : 1) cmp ($b->{forw} eq $b->
+{back} ? 0 : 1)
            # and the reverse lookups
            $a->{back} cmp $b->{back}
        } @{$res{$rank}} ) {

            if( $host->{forw} eq $host->{back} ) {
                printf( "%4d %-15s %s\n", $rank, $host->{ip}, $host->{
+forw} );
            else {
                printf( "%4d %-15s forw=%s back=%s\n", $rank, $host->{
+ip}, $host->{forw}, $host->{back} );
continue {
    print "\n" if scalar @ARGV > 1;

=head1 NAME

mx - Mail Exchanger (MX) information about internet hosts


B<mx> host [...]


Look up the MX records of an internet host I<e.g.> C<> or C
+<>. Take the
resulting records and look up the A records, in order to obtain the nu
+meric IP addresses
of the mail exchangers. Take the IP addresses, and perform reverse loo
+kups on them, to see
how these addresses resolve.


This program depends on L<Net::DNS|Net::DNS>. It also depends on L<Soc
+ket|Socket> but this is usually
bundled with the standard distribution.

This program works correctly for Perl version 5.005_03 and beyond.

=head1 OPTIONS

=over 5



These lookups were performed in October 2003. The exact results return
+ed may vary in the future.


The domain C<[> has a single MX record, with a distance of 0. 
+This points to a host named C<>,
who has the IP address C<>. This address resolves to C<per>



The domain C<> has a single MX record with a distance of 
+5. This points to a host with matching A and PTR records.


   5 back=wanamaker.mail.atl.eart
   5 back=strange.mail.mindspring
   5 back=pickering.mail.mindspri
   5 back=samuel.mail.atl.earthli
   5 back=timothy.mail.atl.earthl
   5 back=gideon.mail.atl.earthli
   5 back=kendall.mail.mindspring
   5 back=cave.mail.atl.earthlink
   5 back=carlin.mail.atl.earthli
   5 back=aaron.mail.atl.earthlin
   5 back=albert.mail.atl.earthli
   5 back=watson.mail.atl.earthli
   5 back=james.mail.atl.earthlin

This domain has a number of mail exchangers, but one lacks any additio
+nal MX information.


    MX lookup error: query timed out
    A lookup error: query timed out

No C<MX> records were found for this domain. The script then tried to 
if an C<A> record existed for the domain but that failed too. You are 
to be able exchange mail with this domain (and if you receive mail fro
+m them
you may as well route it to C</dev/null>). An additional check confirm
+s this:

C<host -t ns> name server name server



  10 back=NXDOMAIN

A domain that has a mail exchanger, but the internet host in question 
+does not have a PTR
record to turn the IP address back into a domain name. Sometimes the s
+ign of a spammer, sometimes
the sign of a clueless admin, or careless or unhelpful ISP.


  10 back=NXDOMAIN

A very interesting domain to receive mail from. You are unlikely to be
+ able to reply.

=head1 SEE ALSO


=head1 BUGS

I once came across a domain that uses CNAMEs for MX records. Ewww! Nau
+ghty! The code deals with this,
but I have lost track of the domain in question, so I can't show an ex
+ample of that. Suggestions


Copyright 2003 David Landgren.

This script is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=head1 AUTHOR

    David Landgren
    aka grinder on perlmonks
    join(chr(64) => qw[david landgren]) . q{.net}


Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: sourcecode [id://297667]
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others scrutinizing the Monastery: (6)
As of 2023-12-03 00:27 GMT
Find Nodes?
    Voting Booth?
    What's your preferred 'use VERSION' for new CPAN modules in 2023?

    Results (20 votes). Check out past polls.