963c067e6f56f827610452cf966f91e321384c03
[netdnsspoof.git] / netdnsspoof
1 #! /usr/bin/perl
2 #
3 #       $Id$
4
5 use strict;
6 use warnings;
7 use Getopt::Long;
8 require IO::Socket::INET;
9 require Net::DNS::Packet;
10 use Fcntl;
11 use Carp qw(cluck confess);
12 use Socket;
13
14
15 my $V=1;
16 $|=1;
17
18 my $D;
19 my $opt_addr;
20 my $opt_port=5353;
21 my $opt_forward_addr="localhost";
22 my $opt_forward_port=53;
23 my $opt_spoof_ip="192.168.1.1";
24 die if !GetOptions(
25                 "a|addr=s",\$opt_addr,
26                 "p|port=s",\$opt_port,
27                   "forward-addr=s",\$opt_forward_addr,
28                   "forward-port=s",\$opt_forward_port,
29                 "i|spoof-ip=s",\$opt_spoof_ip,
30                 "d|debug+",\$D,
31                 );
32 my $forward_host=gethostbyname($opt_forward_addr) or die "resolving $opt_forward_addr: $!";
33 my $forward_addr=sockaddr_in($opt_forward_port,$forward_host) or die "assembling $opt_forward_addr:$opt_forward_port";
34
35 my $sock_udp=IO::Socket::INET->new(
36                 LocalAddr=>$opt_addr,
37                 LocalPort=>$opt_port,
38                 Proto=>"udp",
39                 ) or die "socket(): $!";
40 my $sock_udp_priv=IO::Socket::INET->new(
41                 Proto=>"udp",
42                 ) or die "socket(): $!";
43 ###fcntl($sock_udp,F_SETFL,O_NONBLOCK) or die "fnctl(sock_udp,F_SETFL,O_NONBLOCK)";
44
45 sub id_next()
46 {
47         our $id;
48         $id=int(rand(0x10000)) if !defined $id;
49         $id++;
50         $id&=0xFFFF;
51         return $id;
52 }
53
54 my %sent;
55
56 sub got_query($$)
57 {
58 my($msg,$from_addr)=@_;
59
60         my $query=Net::DNS::Packet->new(\$msg);
61         my $query_id_orig=$query->header()->id();
62         my $query_id=id_next();
63         $query->header()->id($query_id);
64         my $msg_forward=$query->data();
65         $query->header()->id($query_id_orig);   # fix-up back
66         $sent{$query_id}={
67                 "msg"=>$msg,
68                 "from_addr"=>$from_addr,
69                 "query"=>$query,
70                 };
71         warn "registered pending query id $query_id..." if $D;
72         warn "sending forwarded DNS query (mapped id $query_id_orig -> $query_id)..." if $D;
73         send $sock_udp_priv,$msg_forward,0,$forward_addr or cluck "send(): $!";
74 }
75
76 sub check_spoofable($$)
77 {
78 my($reply,$query)=@_;
79
80         warn "checking packet spoofability from the reply... (non-spoofable if silent)" if $D;
81         my @questions=$query->question();
82         return 0 if 1!=@questions;
83         my $question=$questions[0];
84         return 0 if $question->qtype() ne "A"
85                  && $question->qtype() ne "ANY";
86         return 0 if $reply->header()->rcode() ne "NXDOMAIN";
87         warn "packet considered as spoofable." if $D;
88         return 1;
89 }
90
91 sub reply_spoof($$)
92 {
93 my($query_from_addr,$query)=@_;
94
95         warn "assembling spoof reply..." if $D;
96         my $question=($query->question())[0];
97         my $spoof=Net::DNS::Packet->new($question->qname(),$question->qclass(),$question->qtype());
98         $spoof->push("answer",Net::DNS::RR->new(join(" ",
99                         $question->qname(),
100                         3600,   # ttl
101                         $question->qclass(),
102                         "A",    # qtype
103                         $opt_spoof_ip,  # rdata
104                         )));
105         $spoof->header()->rcode("NOERROR");
106         $spoof->header()->aa(0);
107         $spoof->header()->qr(1);
108         $spoof->header()->ra(1);
109         $spoof->header()->rd($query->header()->rd());
110         $spoof->header()->id($query->header()->id());
111         my $msg_spoof=$spoof->data();
112         warn "sending spoof reply..." if $D;
113         send $sock_udp,$msg_spoof,0,$query_from_addr or cluck "send(): $!";
114         if ($V) {
115                 my($query_from_addr_port,$query_from_addr_host)=sockaddr_in($query_from_addr);
116                 my $query_from_addr_name=inet_ntoa $query_from_addr_host;
117                 print localtime()." spoof:"
118                                 ." from=$query_from_addr_name:$query_from_addr_port"
119                                 ." qname=".$question->qname()
120                                 ." ip=".$opt_spoof_ip
121                                 ."\n";
122                 }
123 }
124
125 sub got_forward_reply($)
126 {
127 my($msg)=@_;
128
129         warn "parsing reply from our forwarded DNS..." if $D;
130         my $reply=Net::DNS::Packet->new(\$msg);
131         my $reply_id=$reply->header()->id();
132         my($query_msg,$query_from_addr,$query);
133         if (my $hash=$sent{$reply_id}) {
134                 delete $sent{$reply_id};
135                 $query_msg      =$hash->{"msg"};
136                 $query_from_addr=$hash->{"from_addr"};
137                 $query          =$hash->{"query"};
138                 warn "deleted pending record with id $reply_id." if $D;
139                 }
140         else {
141                 warn "Got DNS reply for unknown packet id $reply_id";
142                 return;
143                 }
144         if (check_spoofable($reply,$query)) {
145                 reply_spoof $query_from_addr,$query;
146                 return;
147                 }
148         $reply->header()->id($query->header()->id());
149         my $reply_back=$reply->data();
150         warn "passing reply back to the original query host..." if $D;
151         send $sock_udp,$reply_back,0,$query_from_addr or cluck "send(): $!";
152 }
153
154 $V and print localtime()." START\n";
155 for (;;) {
156         my $rfds="";
157         vec($rfds,fileno($sock_udp),1)=1;
158         vec($rfds,fileno($sock_udp_priv),1)=1;
159         warn "select(2)..." if $D;
160         my $got=select $rfds,undef(),undef(),undef();
161         warn "got from select." if $D;
162         die "Invalid select(2): ".Dumper($got) if !defined $got || $got<0;
163
164         for my $sock ($sock_udp,$sock_udp_priv) {
165                 next if !vec($rfds,fileno($sock),1);
166                 my $msg;
167                 defined(my $from_addr=recv $sock,$msg,0x1000,0) or do { cluck "recv(): $!"; next; };
168                 warn "got packet." if $D;
169                 my($from_addr_port,$from_addr_host)=sockaddr_in($from_addr);
170
171                 if ($from_addr_host eq $forward_host && $from_addr_port eq $opt_forward_port) {
172                         warn "packet returned from forwarding DNS..." if $D;
173                         if ($sock eq $sock_udp) {
174                                 warn "packet returned from forwarding DNS forbidden from the main listening socket";
175                                 next;
176                                 }
177                         got_forward_reply $msg;
178                         }
179                 else {
180                         warn "original query..." if $D;
181                         if ($sock eq $sock_udp_priv) {
182                                 warn "original query forbidden from the private forwarding socket";
183                                 next;
184                                 }
185                         got_query $msg,$from_addr;
186                         }
187                 }
188         }