xref: /netbsd-src/external/mpl/bind/dist/bin/tests/system/digdelv/ans5/ans.pl (revision fb5eed702691094bd687fbf1ded189c87457cd35)
1#!/usr/bin/perl
2#
3# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
4#
5# This Source Code Form is subject to the terms of the Mozilla Public
6# License, v. 2.0. If a copy of the MPL was not distributed with this
7# file, you can obtain one at https://mozilla.org/MPL/2.0/.
8#
9# See the COPYRIGHT file distributed with this work for additional
10# information regarding copyright ownership.
11
12# This is a TCP-only DNS server whose aim is to facilitate testing how dig
13# copes with prematurely closed TCP connections.
14#
15# This server can be configured (through a separate control socket) with a
16# series of responses to send for subsequent incoming TCP DNS queries.  Only
17# one query is handled before closing each connection.  In order to keep things
18# simple, the server is not equipped with any mechanism for handling malformed
19# queries.
20#
21# Available response types are defined in the %response_types hash in the
22# getAnswerSection() function below.  Each RR returned is generated dynamically
23# based on the QNAME found in the incoming query.
24
25use IO::File;
26use Net::DNS;
27use Net::DNS::Packet;
28
29use strict;
30
31# Ignore SIGPIPE so we won't fail if peer closes a TCP socket early
32local $SIG{PIPE} = 'IGNORE';
33
34# Flush logged output after every line
35local $| = 1;
36
37my $server_addr = "10.53.0.5";
38if (@ARGV > 0) {
39	$server_addr = @ARGV[0];
40}
41
42my $mainport = int($ENV{'PORT'});
43if (!$mainport) { $mainport = 5300; }
44my $ctrlport = int($ENV{'EXTRAPORT1'});
45if (!$ctrlport) { $ctrlport = 5301; }
46
47my $ctlsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
48   LocalPort => $ctrlport, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!";
49
50my $tcpsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
51   LocalPort => $mainport, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!";
52
53my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!";
54print $pidf "$$\n" or die "cannot write pid file: $!";
55$pidf->close or die "cannot close pid file: $!";;
56sub rmpid { unlink "ans.pid"; exit 1; };
57
58$SIG{INT} = \&rmpid;
59$SIG{TERM} = \&rmpid;
60
61my @response_sequence = ("complete_axfr");
62my $connection_counter = 0;
63
64# Return the next answer type to send, incrementing the connection counter and
65# making sure the latter does not exceed the size of the array holding the
66# configured response sequence.
67sub getNextResponseType {
68	my $response_type = $response_sequence[$connection_counter];
69
70	$connection_counter++;
71	$connection_counter %= scalar(@response_sequence);
72
73	return $response_type;
74}
75
76# Return an array of resource records comprising the answer section of a given
77# response type.
78sub getAnswerSection {
79	my ($response_type, $qname) = @_;
80
81	my %response_types = (
82		no_response => [],
83
84		partial_axfr => [
85			Net::DNS::RR->new("$qname 300 IN SOA . . 0 0 0 0 300"),
86			Net::DNS::RR->new("$qname NS ."),
87		],
88
89		complete_axfr => [
90			Net::DNS::RR->new("$qname 300 IN SOA . . 0 0 0 0 300"),
91			Net::DNS::RR->new("$qname NS ."),
92			Net::DNS::RR->new("$qname 300 IN SOA . . 0 0 0 0 300"),
93		],
94	);
95
96	return $response_types{$response_type};
97}
98
99
100# Generate a Net::DNS::Packet containing the response to send on the current
101# TCP connection.  If the answer section of the response is determined to be
102# empty, no data will be sent on the connection at all (immediate EOF).
103sub generateResponse {
104	my ($buf) = @_;
105	my $request;
106
107	if ($Net::DNS::VERSION > 0.68) {
108		$request = new Net::DNS::Packet(\$buf, 0);
109		$@ and die $@;
110	} else {
111		my $err;
112		($request, $err) = new Net::DNS::Packet(\$buf, 0);
113		$err and die $err;
114	}
115
116	my @questions = $request->question;
117	my $qname = $questions[0]->qname;
118	my $qtype = $questions[0]->qtype;
119	my $qclass = $questions[0]->qclass;
120	my $id = $request->header->id;
121
122	my $packet = new Net::DNS::Packet($qname, $qtype, $qclass);
123	$packet->header->qr(1);
124	$packet->header->aa(1);
125	$packet->header->id($id);
126
127	my $response_type = getNextResponseType();
128	my $answers = getAnswerSection($response_type, $qname);
129	for my $rr (@$answers) {
130		$packet->push("answer", $rr);
131	}
132
133	print "    Sending \"$response_type\" response\n";
134
135	return $packet->data if @$answers;
136}
137
138my $rin;
139my $rout;
140for (;;) {
141	$rin = '';
142	vec($rin, fileno($ctlsock), 1) = 1;
143	vec($rin, fileno($tcpsock), 1) = 1;
144
145	select($rout = $rin, undef, undef, undef);
146
147	if (vec($rout, fileno($ctlsock), 1)) {
148		my $conn = $ctlsock->accept;
149		@response_sequence = split(' ', $conn->getline);
150		$connection_counter = 0;
151		print "Response sequence set to: @response_sequence\n";
152		$conn->close;
153	} elsif (vec($rout, fileno($tcpsock), 1)) {
154		my $buf;
155		my $lenbuf;
156		my $conn = $tcpsock->accept;
157		my $n = $conn->sysread($lenbuf, 2);
158		die unless $n == 2;
159		my $len = unpack("n", $lenbuf);
160		$n = $conn->sysread($buf, $len);
161		die unless $n == $len;
162		print "TCP request\n";
163		my $response = generateResponse($buf);
164		if ($response) {
165			$len = length($response);
166			$n = $conn->syswrite(pack("n", $len), 2);
167			$n = $conn->syswrite($response, $len);
168			print "    Sent: $n chars via TCP\n";
169		} else {
170			print "    No response sent\n";
171		}
172		$conn->close;
173	}
174}
175