#! /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; } } }