All Articles

Finding Available Short Domain Names

It is really nice to have a short domain name. Unfortunately, most of these short domains have long ago been registered in the most popular TLDs like .com. Domain name speculators simply have registered all known words and even increasingly all of the short combinations of domains in a particular TLD.

But before assuming all is lost there should be a simple way to check to see which domains could be possibly available. I’m backing away from claiming this method that describe will guarantee availability of a particular domain because the only true way to test for availability is to attempt to register the domain at a registrar. We can eliminate many potential candidates that we know are already registered it will make the list to manually check must smaller and manageable.

Before we can check for domain availability we need to generate a list of potential candidate domains to match our specifications with regards to length.

A valid domain has a few restrictions, it must consist of the characters from a-z and 0-9 it can also include a hyphen. Domain’s can’t be fully numeric, they also can’t start with a hyphen.

This perl program will generate domain names that are valid and have a length that you specify.

#!/usr/bin/perl
# Generate a list of domain names.
# Author: Rusty Conover <rusty@luckydinosaur.com>
#
# example:
# perl generate-domain-names.pl .com 1 2
#
# Will generate domain names that are 1 to 2 characters long for the .com TLD
#
use strict;
use warnings;
use Algorithm::Combinatorics;

my @letters = ('a' .. 'z', '0' .. '9', '-');

my $tld = shift @ARGV;
my $minimum_length = shift @ARGV;
my $maximum_length = shift @ARGV;

# Validate the command line parameters

defined($tld) || die("No TLD specified, example: .com");
defined($minimum_length) || die("No minimum length specified");
$minimum_length =~ /^\d+$/ || die("Minimum length is not an integer");
defined($maximum_length) || die("No maximum length specified");
$maximum_length =~ /^\d+$/ || die("Maximum length is not an integer");

$minimum_length <= $maximum_length ||
    die("Minimum length is not smaller than maximum_length");

sub generate_domain_name {
    my $length = shift;
    my $gen = Algorithm::Combinatorics::variations_with_repetition(\@letters, $length);
    while(my $combo = $gen->next) {
	my $v = join("", @$combo);

	# Domain's can't start with a hypen nor be all numeric
	if($combo->[0] eq '-' || $v =~ /^\d+$/) {
	    next;
	}

	print $v . $tld . "\n";
    }
}


for(my $i = $minimum_length; $i < $maximum_length+1; $i++) {
    generate_domain_name($i);
}

Running the program for domains of length 1 produce output like:

a.com
b.com
c.com
d.com
e.com

Length 3 is:

br8.com
br9.com
br-.com
bsa.com
bsb.com
bsc.com
bsd.com
bse.com
bsf.com
bsg.com
bsh.com
bsi.com
bsj.com
bsk.com

Now that the list of domains to verify has been produced, the easiest and least network intensive way to check to see if a domain is registered is to see if there is a NS record in the zone server for the TLD.

DNS produces results by asking servers from the root down to the final domain level recursively. For instance if we want to get the IP address of nytimes.com, our DNS client will first ask the root zone, which DNS servers resolve domains for .com, then our client asks one of the .com servers which name server responds for nytimes and finally the DNS client asks the nytimes.com DNS server to get the final result.

The main trick of this method is only asking the TLD’s servers if a zone exists and not verifying the result down the entire tree. Frequently someone will register a domain but doesn’t add corresponding DNS entries for the domain at the final level. This may be due to the lack of wanting to handle requests, or other laziness. Another reason to only ask the TLD servers is that the infrastructure of the TLD servers is scaled to accommodate many orders of magnitude more DNS queries then the DNS servers of a particular zone. So if we’re going to resolve 30,000 domains, it’s best to keep things at the TLD.

The following perl program does exactly that. You can run it like this:

perl generate-domain-names.pl .com 2 2  | perl resolve-domains-at-tld.pl

It will produce output like:

9w.com 1
9x.com 1
9y.com 1
9z.com 1
9-.com 0

It first lists the domain name then an integer reflecting if there are DNS NS records present for that domain. If there are DNS NS record present it means the domain is registered. So use grep to determine domains that don’t have NS records, then go to your favorite registrar and try to register them.

#!/usr/bin/perl
# Resolve many domain names at a TLD for the zone asynchronously.
#
# Author: Rusty Conover <rusty@luckydinosaur.com>
#
use strict;
use warnings;
use AnyEvent::DNS;
use AnyEvent;

# This is a resolver that uses Google's public dns, we're going to be using it to
# look up the servers for the TLD and translating those hostnames into ip addresses.

my $google_resolver = new AnyEvent::DNS
    server => [map { AnyEvent::Socket::parse_address($_) } ('8.8.8.8', '8.8.4.4')];

# Get a list of hostnames that are the NS servers for a particular TLD.
sub get_tld_servers {
    my $tld = shift;
    my $cv = AnyEvent->condvar;
    my @servers;
    my $handle_reply =sub {
        my $result = shift;
        if($result->{rc} eq 'noerror') {
            push @servers, map { $_->[4] } @{$result->{an}};
        }
        $cv->send;
    };
    $google_resolver->request ({ rd => 0,  qd => [ [$tld, "ns"] ]}, $handle_reply);
    $cv->recv;
    return @servers;
}

# Resolve a hostname into an ip address into the socket address format.
sub resolve_to_ip_addresses {
    my @servers = @_;
    my $cv = AnyEvent->condvar;
    my $lc = 0;
    my @addresses;
    foreach my $hostname (@servers) {
        $lc++;
        AnyEvent::DNS::a $hostname, sub {
            my $result = shift;
            push @addresses, AnyEvent::Socket::parse_address($result);
            if(!--$lc) {
                $cv->send;
            }
        };
    }
    $cv->recv;
    return @addresses;
}

sub resolve_domains {
    my $resolver = shift;
    my $domains = shift;

    my $cv = AnyEvent->condvar;

    my $started = 0;
    my $finished = 0;
    my $queue_done = 0;

    my $handle_domain_reply =sub {
        my $result = shift;

        my $domain = $result->{qd}->[0]->[0];
        if($result->{rc} eq 'nxdomain') {
            print "$domain 0\n";
        } elsif($result->{rc} eq 'noerror') {
            print "$domain 1\n";
        } else {
            die("Unknown result: " . $result->{rc});
        }

        ++$finished;
        if($finished % 500 == 0) {
            print STDERR "Finished $finished - $domain\n";
        }

        if($queue_done && $finished == $started) {
            $cv->send;
        }
    };


    foreach my $domain (@$domains) {
        $resolver->request ({ rd => 0,  qd => [ [$domain, "ns"] ]},
            $handle_domain_reply);
        $started++;
    }
    $queue_done = 1;

    $cv->recv;
}


# Read the first domain so we can parse out the tld.
my $first_domain = <>;
chomp($first_domain);

# The TLD is everything after the first dot for our purposes.
my $tld = $first_domain;
$tld =~ s/^[^.]+\.//;

# Look up the TLD servers and get their ip addresses.
my @tld_servers = get_tld_servers($tld);
scalar(@tld_servers) > 0 || die("No TLD servers found for tld: $tld");
my @resolver_addresses = resolve_to_ip_addresses(@tld_servers);
scalar(@resolver_addresses) > 0 ||
    die("No IP addresses found for tld servers in tld: $tld");

# The number of requests that can be outstanding to a DNS server at one time.
my $batch_size = 300;

# Create a new resolver that talks directly to the TLD.
my $tld_resolver = new AnyEvent::DNS
    server => \@resolver_addresses,
    max_outstanding => $batch_size;

# Read all of the domains we're supposed to resolve, but process them
# in batches of size $batch_size, this is because sometimes you can't
# just push out 300,000 DNS requests at one time and expect to receive
# all of the replies, change $batch_size to suit your needs.

while(1) {
    my @batch;
    my $limited =0;
    while(my $line = <>) {
        chomp($line);

        if(defined($first_domain)) {
            push @batch, $first_domain;
            $first_domain = undef;
        }

        push @batch, $line;
        if(scalar(@batch) == $batch_size) {
            $limited = 1;
            last;
        }
    }

    if(scalar(@batch)) {
        resolve_domains($tld_resolver, \@batch);
    }

    if(!$limited) {
        last;
    }
}