9 require IO::Socket::INET;
10 require Net::DNS::Packet;
12 use Carp qw(cluck confess);
22 my $opt_forward_addr="localhost";
23 my $opt_forward_port=53;
24 my $opt_spoof_ip="192.168.1.1";
26 "a|addr=s",\$opt_addr,
27 "p|port=s",\$opt_port,
28 "forward-addr=s",\$opt_forward_addr,
29 "forward-port=s",\$opt_forward_port,
30 "i|spoof-ip=s",\$opt_spoof_ip,
33 my $forward_host=gethostbyname($opt_forward_addr) or die "resolving $opt_forward_addr: $!";
34 my $forward_addr=sockaddr_in($opt_forward_port,$forward_host) or die "assembling $opt_forward_addr:$opt_forward_port";
36 my $sock_udp=IO::Socket::INET->new(
40 ) or die "socket(): $!";
41 my $sock_udp_priv=IO::Socket::INET->new(
43 ) or die "socket(): $!";
44 ###fcntl($sock_udp,F_SETFL,O_NONBLOCK) or die "fnctl(sock_udp,F_SETFL,O_NONBLOCK)";
49 $id=int(rand(0x10000)) if !defined $id;
59 my($msg,$from_addr)=@_;
61 my $query=Net::DNS::Packet->new(\$msg);
62 my $query_id_orig=$query->header()->id();
63 my $query_id=id_next();
64 $query->header()->id($query_id);
65 my $msg_forward=$query->data();
66 $query->header()->id($query_id_orig); # fix-up back
69 "from_addr"=>$from_addr,
72 warn "registered pending query id $query_id..." if $D;
73 warn "sending forwarded DNS query (mapped id $query_id_orig -> $query_id)..." if $D;
74 send $sock_udp_priv,$msg_forward,0,$forward_addr or cluck "send(): $!";
77 sub check_spoofable($$)
81 warn "checking packet spoofability from the reply... (non-spoofable if silent)" if $D;
82 my @questions=$query->question();
83 return 0 if 1!=@questions;
84 my $question=$questions[0];
85 return 0 if $question->qtype() ne "A"
86 && $question->qtype() ne "ANY";
87 return 0 if $reply->header()->rcode() ne "NXDOMAIN";
88 warn "packet considered as spoofable." if $D;
94 my($query_from_addr,$query)=@_;
96 warn "assembling spoof reply..." if $D;
97 my $question=($query->question())[0];
98 my $spoof=Net::DNS::Packet->new($question->qname(),$question->qclass(),$question->qtype());
99 $spoof->push("answer",Net::DNS::RR->new(join(" ",
104 $opt_spoof_ip, # rdata
106 $spoof->header()->rcode("NOERROR");
107 $spoof->header()->aa(0);
108 $spoof->header()->qr(1);
109 $spoof->header()->ra(1);
110 $spoof->header()->rd($query->header()->rd());
111 $spoof->header()->id($query->header()->id());
112 my $msg_spoof=$spoof->data();
113 warn "sending spoof reply..." if $D;
114 send $sock_udp,$msg_spoof,0,$query_from_addr or cluck "send(): $!";
116 my($query_from_addr_port,$query_from_addr_host)=sockaddr_in($query_from_addr);
117 my $query_from_addr_name=inet_ntoa $query_from_addr_host;
118 print localtime()." spoof:"
119 ." from=$query_from_addr_name:$query_from_addr_port"
120 ." qname=".$question->qname()
121 ." ip=".$opt_spoof_ip
126 sub got_forward_reply($)
130 warn "parsing reply from our forwarded DNS..." if $D;
131 my $reply=Net::DNS::Packet->new(\$msg);
132 my $reply_id=$reply->header()->id();
133 my($query_msg,$query_from_addr,$query);
134 if (my $hash=$sent{$reply_id}) {
135 delete $sent{$reply_id};
136 $query_msg =$hash->{"msg"};
137 $query_from_addr=$hash->{"from_addr"};
138 $query =$hash->{"query"};
139 warn "deleted pending record with id $reply_id." if $D;
142 warn "Got DNS reply for unknown packet id $reply_id";
145 if (check_spoofable($reply,$query)) {
146 reply_spoof $query_from_addr,$query;
149 $reply->header()->id($query->header()->id());
150 my $reply_back=$reply->data();
151 warn "passing reply back to the original query host..." if $D;
152 send $sock_udp,$reply_back,0,$query_from_addr or cluck "send(): $!";
155 $V and print localtime()." START\n";
158 vec($rfds,fileno($sock_udp),1)=1;
159 vec($rfds,fileno($sock_udp_priv),1)=1;
160 warn "select(2)..." if $D;
161 my $got=select $rfds,undef(),undef(),undef();
162 warn "got from select." if $D;
163 die "Invalid select(2): ".Dumper($got) if !defined $got || $got<0;
165 for my $sock ($sock_udp,$sock_udp_priv) {
166 next if !vec($rfds,fileno($sock),1);
168 defined(my $from_addr=recv $sock,$msg,0x1000,0) or do { cluck "recv(): $!"; next; };
169 warn "got packet." if $D;
170 my($from_addr_port,$from_addr_host)=sockaddr_in($from_addr);
172 if ($from_addr_host eq $forward_host && $from_addr_port eq $opt_forward_port) {
173 warn "packet returned from forwarding DNS..." if $D;
174 if ($sock eq $sock_udp) {
175 warn "packet returned from forwarding DNS forbidden from the main listening socket";
178 got_forward_reply $msg;
181 warn "original query..." if $D;
182 if ($sock eq $sock_udp_priv) {
183 warn "original query forbidden from the private forwarding socket";
186 got_query $msg,$from_addr;