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