1 #!/usr/bin/env perl 2 3 # Copyright (C) Internet Systems Consortium, Inc. ("ISC") 4 # 5 # SPDX-License-Identifier: MPL-2.0 6 # 7 # This Source Code Form is subject to the terms of the Mozilla Public 8 # License, v. 2.0. If a copy of the MPL was not distributed with this 9 # file, you can obtain one at https://mozilla.org/MPL/2.0/. 10 # 11 # See the COPYRIGHT file distributed with this work for additional 12 # information regarding copyright ownership. 13 14 use strict; 15 use warnings; 16 17 use IO::File; 18 use IO::Socket; 19 use Getopt::Long; 20 use Net::DNS; 21 use Time::HiRes qw(usleep nanosleep); 22 23 my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!"; 24 print $pidf "$$\n" or die "cannot write pid file: $!"; 25 $pidf->close or die "cannot close pid file: $!"; 26 sub rmpid { unlink "ans.pid"; exit 1; }; 27 28 $SIG{INT} = \&rmpid; 29 $SIG{TERM} = \&rmpid; 30 31 my $localaddr = "10.53.0.8"; 32 33 my $localport = int($ENV{'PORT'}); 34 if (!$localport) { $localport = 5300; } 35 36 my $udpsock = IO::Socket::INET->new(LocalAddr => "$localaddr", 37 LocalPort => $localport, Proto => "udp", Reuse => 1) or die "$!"; 38 39 # 40 # YWH records 41 # 42 my $ywhSOA = "target.stale 300 IN SOA . . 0 0 0 0 300"; 43 my $ywhNS = "target.stale 300 IN NS ns.target.stale"; 44 my $ywhA = "ns.target.stale 300 IN A $localaddr"; 45 my $ywhWWW = "www.target.stale 2 IN A 10.0.0.1"; 46 47 sub reply_handler { 48 my ($qname, $qclass, $qtype) = @_; 49 my ($rcode, @ans, @auth, @add); 50 51 print ("request: $qname/$qtype\n"); 52 STDOUT->flush(); 53 54 # Control what response we send. 55 if ($qname eq "update" ) { 56 if ($qtype eq "TXT") { 57 $ywhWWW = "www.target.stale 2 IN A 10.0.0.2"; 58 my $rr = new Net::DNS::RR("$qname 0 $qclass TXT \"update\""); 59 push @ans, $rr; 60 } 61 $rcode = "NOERROR"; 62 return ($rcode, \@ans, \@auth, \@add, { aa => 1 }); 63 } elsif ($qname eq "restore" ) { 64 if ($qtype eq "TXT") { 65 $ywhWWW = "www.target.stale 2 IN A 10.0.0.1"; 66 my $rr = new Net::DNS::RR("$qname 0 $qclass TXT \"restore\""); 67 push @ans, $rr; 68 } 69 $rcode = "NOERROR"; 70 return ($rcode, \@ans, \@auth, \@add, { aa => 1 }); 71 } 72 73 if ($qname eq "target.stale") { 74 if ($qtype eq "SOA") { 75 my $rr = new Net::DNS::RR($ywhSOA); 76 push @ans, $rr; 77 } elsif ($qtype eq "NS") { 78 my $rr = new Net::DNS::RR($ywhNS); 79 push @ans, $rr; 80 $rr = new Net::DNS::RR($ywhA); 81 push @add, $rr; 82 } 83 $rcode = "NOERROR"; 84 } elsif ($qname eq "ns.target.stale") { 85 if ($qtype eq "A") { 86 my $rr = new Net::DNS::RR($ywhA); 87 push @ans, $rr; 88 } else { 89 my $rr = new Net::DNS::RR($ywhSOA); 90 push @auth, $rr; 91 } 92 $rcode = "NOERROR"; 93 } elsif ($qname eq "www.target.stale") { 94 if ($qtype eq "A") { 95 my $rr = new Net::DNS::RR($ywhWWW); 96 push @ans, $rr; 97 } else { 98 my $rr = new Net::DNS::RR($ywhSOA); 99 push @auth, $rr; 100 } 101 $rcode = "NOERROR"; 102 } else { 103 my $rr = new Net::DNS::RR($ywhSOA); 104 push @auth, $rr; 105 $rcode = "NXDOMAIN"; 106 } 107 108 # mark the answer as authoritative (by setting the 'aa' flag) 109 return ($rcode, \@ans, \@auth, \@add, { aa => 1 }); 110 } 111 112 GetOptions( 113 'port=i' => \$localport, 114 ); 115 116 my $rin; 117 my $rout; 118 119 for (;;) { 120 $rin = ''; 121 vec($rin, fileno($udpsock), 1) = 1; 122 123 select($rout = $rin, undef, undef, undef); 124 125 if (vec($rout, fileno($udpsock), 1)) { 126 my ($buf, $request, $err); 127 $udpsock->recv($buf, 512); 128 129 if ($Net::DNS::VERSION > 0.68) { 130 $request = new Net::DNS::Packet(\$buf, 0); 131 $@ and die $@; 132 } else { 133 my $err; 134 ($request, $err) = new Net::DNS::Packet(\$buf, 0); 135 $err and die $err; 136 } 137 138 my @questions = $request->question; 139 my $qname = $questions[0]->qname; 140 my $qclass = $questions[0]->qclass; 141 my $qtype = $questions[0]->qtype; 142 my $id = $request->header->id; 143 144 my ($rcode, $ans, $auth, $add, $headermask) = reply_handler($qname, $qclass, $qtype); 145 146 if (!defined($rcode)) { 147 print " Silently ignoring query\n"; 148 next; 149 } 150 151 my $reply = Net::DNS::Packet->new(); 152 $reply->header->qr(1); 153 $reply->header->aa(1) if $headermask->{'aa'}; 154 $reply->header->id($id); 155 $reply->header->rcode($rcode); 156 $reply->push("question", @questions); 157 $reply->push("answer", @$ans) if $ans; 158 $reply->push("authority", @$auth) if $auth; 159 $reply->push("additional", @$add) if $add; 160 161 my $num_chars = $udpsock->send($reply->data); 162 print " Sent $num_chars bytes via UDP\n"; 163 } 164 } 165