Skip to content

Instantly share code, notes, and snippets.

@rustyconover
Created March 2, 2014 19:40
Show Gist options
  • Select an option

  • Save rustyconover/9312610 to your computer and use it in GitHub Desktop.

Select an option

Save rustyconover/9312610 to your computer and use it in GitHub Desktop.
#!/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;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment