Initial version.
authorshort <>
Thu, 14 Oct 2004 08:14:48 +0000 (08:14 +0000)
committershort <>
Thu, 14 Oct 2004 08:14:48 +0000 (08:14 +0000)
netdnsspoof [new file with mode: 0755]

diff --git a/netdnsspoof b/netdnsspoof
new file mode 100755 (executable)
index 0000000..65b3b83
--- /dev/null
@@ -0,0 +1,174 @@
+#! /usr/bin/perl
+#
+#      $Id$
+
+use strict;
+use warnings;
+use Getopt::Long;
+require IO::Socket::INET;
+require Net::DNS::Packet;
+use Fcntl;
+use Carp qw(cluck confess);
+use Socket;
+
+
+my $D;
+my $opt_addr;
+my $opt_port=5353;
+my $opt_forward_addr="localhost";
+my $opt_forward_port=53;
+my $opt_spoof_ip="192.196.60.1";
+die if !GetOptions(
+               "a|addr=s",\$opt_addr,
+               "p|port=s",\$opt_port,
+                 "forward-addr=s",\$opt_forward_addr,
+                 "forward-port=s",\$opt_forward_port,
+               "i|spoof-ip=s",\$opt_spoof_ip,
+               "d|debug+",\$D,
+               );
+my $forward_host=gethostbyname($opt_forward_addr) or die "resolving $opt_forward_addr: $!";
+my $forward_addr=sockaddr_in($opt_forward_port,$forward_host) or die "assembling $opt_forward_addr:$opt_forward_port";
+
+my $sock_udp=IO::Socket::INET->new(
+               LocalAddr=>$opt_addr,
+               LocalPort=>$opt_port,
+               Proto=>"udp",
+               ) or die "socket(): $!";
+my $sock_udp_priv=IO::Socket::INET->new(
+               Proto=>"udp",
+               ) or die "socket(): $!";
+###fcntl($sock_udp,F_SETFL,O_NONBLOCK) or die "fnctl(sock_udp,F_SETFL,O_NONBLOCK)";
+
+sub id_next()
+{
+       our $id;
+       $id=int(rand(0x10000)) if !defined $id;
+       $id++;
+       $id&=0xFFFF;
+       return $id;
+}
+
+my %sent;
+
+sub got_query($$)
+{
+my($msg,$from_addr)=@_;
+
+       my $query=Net::DNS::Packet->new(\$msg);
+       my $query_id_orig=$query->header()->id();
+       my $query_id=id_next();
+       $query->header()->id($query_id);
+       my $msg_forward=$query->data();
+       $query->header()->id($query_id_orig);   # fix-up back
+       $sent{$query_id}={
+               "msg"=>$msg,
+               "from_addr"=>$from_addr,
+               "query"=>$query,
+               };
+       warn "registered pending query id $query_id..." if $D;
+       warn "sending forwarded DNS query (mapped id $query_id_orig -> $query_id)..." if $D;
+       send $sock_udp_priv,$msg_forward,0,$forward_addr or cluck "send(): $!";
+}
+
+sub check_spoofable($$)
+{
+my($reply,$query)=@_;
+
+       warn "checking packet spoofability from the reply... (non-spoofable if silent)" if $D;
+       my @questions=$query->question();
+       return 0 if 1!=@questions;
+       my $question=$questions[0];
+       return 0 if $question->qtype() ne "A"
+                && $question->qtype() ne "ANY";
+       return 0 if $reply->header()->rcode() ne "NXDOMAIN";
+       warn "packet considered as spoofable." if $D;
+       return 1;
+}
+
+sub reply_spoof($$)
+{
+my($query_from_addr,$query)=@_;
+
+       warn "assembling spoof reply..." if $D;
+       my $question=($query->question())[0];
+       my $spoof=Net::DNS::Packet->new($question->qname(),$question->qclass(),$question->qtype());
+       $spoof->push("answer",Net::DNS::RR->new(join(" ",
+                       $question->qname(),
+                       3600,   # ttl
+                       $question->qclass(),
+                       "A",    # qtype
+                       $opt_spoof_ip,  # rdata
+                       )));
+       $spoof->header()->rcode("NOERROR");
+       $spoof->header()->aa(0);
+       $spoof->header()->qr(1);
+       $spoof->header()->ra(1);
+       $spoof->header()->rd($query->header()->rd());
+       $spoof->header()->id($query->header()->id());
+       my $msg_spoof=$spoof->data();
+       warn "sending spoof reply..." if $D;
+       send $sock_udp,$msg_spoof,0,$query_from_addr or cluck "send(): $!";
+}
+
+sub got_forward_reply($)
+{
+my($msg)=@_;
+
+       warn "parsing reply from our forwarded DNS..." if $D;
+       my $reply=Net::DNS::Packet->new(\$msg);
+       my $reply_id=$reply->header()->id();
+       my($query_msg,$query_from_addr,$query);
+       if (my $hash=$sent{$reply_id}) {
+               delete $sent{$reply_id};
+               $query_msg      =$hash->{"msg"};
+               $query_from_addr=$hash->{"from_addr"};
+               $query          =$hash->{"query"};
+               warn "deleted pending record with id $reply_id." if $D;
+               }
+       else {
+               warn "Got DNS reply for unknown packet id $reply_id";
+               return;
+               }
+       if (check_spoofable($reply,$query)) {
+               reply_spoof $query_from_addr,$query;
+               return;
+               }
+       $reply->header()->id($query->header()->id());
+       my $reply_back=$reply->data();
+       warn "parsing reply back to the original query host..." if $D;
+       send $sock_udp,$reply_back,0,$query_from_addr or cluck "send(): $!";
+}
+
+for (;;) {
+       my $rfds="";
+       vec($rfds,fileno($sock_udp),1)=1;
+       vec($rfds,fileno($sock_udp_priv),1)=1;
+       warn "select(2)..." if $D;
+       my $got=select $rfds,undef(),undef(),undef();
+       warn "got from select." if $D;
+       die "Invalid select(2): ".Dumper($got) if !defined $got || $got<0;
+
+       for my $sock ($sock_udp,$sock_udp_priv) {
+               my $msg;
+               defined(my $from_addr=recv $sock,$msg,0x1000,0) or do { cluck "recv(): $!"; next; };
+               warn "got packet." if $D;
+               my($from_addr_port,$from_addr_host)=sockaddr_in($from_addr);
+
+               if ($from_addr_host eq $forward_host && $from_addr_port eq $opt_forward_port) {
+                       warn "packet returned from forwarding DNS..." if $D;
+                       if ($sock eq $sock_udp) {
+                               warn "packet returned from forwarding DNS forbidden from the main listening socket";
+                               next;
+                               }
+                       got_forward_reply $msg;
+                       }
+               else {
+                       warn "original query..." if $D;
+                       if ($sock eq $sock_udp_priv) {
+                               warn "original query forbidden from the private forwarding socket";
+                               next;
+                               }
+                       got_query $msg,$from_addr;
+                       }
+               }
+       }