#!/usr/local/bin/perl

use strict;
use Net::DNS;
use Net::DNS::Nameserver;
use IO::Socket;

sub usage {
	print ("$0 is a program for analysing DNS id predictability.\n");
	print ("It must be run on a name server authoritative for a zone.\n");
	print ("It sends DNS queries to a target\n");
	print ("name server and listens for recursive queries from the target.\n");
	print ("The transaction IDs are printed to STDOUT");
	print ("usage: $0 target times askabout\n");
	print ("Example: $0 ns1.example.com 100 .cache-poisoning.net\n");
}

my($target,$times, $askabout) = @ARGV;

$askabout= ".cache-poisoning.net" unless($askabout);
$times = 100 unless($times);

if(!$target) {
	usage();
	die("You must specify target nameserver\n");
}


if(fork) {
	# Send queries
	my $client = IO::Socket::INET->new(PeerAddr => $target,
                                           PeerPort => 53,
                                	   Proto   => "udp")
    		or die "Couldn't be a udp client on port 53 : $@\n";

	for(my $i=0; $i<$times; $i++) {
		my $packet = Net::DNS::Packet->new("id$i$$".$askabout);
		$client->send($packet->data()) or warn "Failed sending packet: $!";		
	}

	wait;
} else {
#	# Listen for replies

	my @ids;

	sub reply_handler {
        	my ($qname, $qclass, $qtype, $peerhost, $query) = @_;
		my ($rcode, @ans, @auth, @add);
         
		$rcode = "NXDOMAIN";
         
		push @ids, $query->header->id;
		# mark the answer as authoritive (by setting the 'aa' flag
		return ($rcode, \@ans, \@auth, \@add, { aa => 1 });
	};

	eval {
		$SIG{ALRM} = sub { die "timeout\n"; };
		alarm(10);

		my $ns = Net::DNS::Nameserver->new(
			LocalAddr        => "0.0.0.0",
        		LocalPort        => "53",
        		ReplyHandler =>  \&reply_handler,
        		Verbose          => 0
		) || die;
		$ns->main_loop();
	};
	if ($@ !~ /^timeout/) { alarm(0); die $@; };

	print join "\n", @ids;
	print "\n";

	exit 0;
}

