mirror of
https://github.com/isc-projects/bind9.git
synced 2026-05-21 17:41:35 -04:00
Reimplement xfer/ans5 using ControllableAsyncServer
Remove the last usage of the `ans.pl` server and the server itself.
This commit is contained in:
parent
37b0502cf4
commit
f9ed3650ac
16 changed files with 445 additions and 742 deletions
|
|
@ -1,596 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
#
|
||||
# This is the name server from hell. It provides canned
|
||||
# responses based on pattern matching the queries, and
|
||||
# can be reprogrammed on-the-fly over a TCP connection.
|
||||
#
|
||||
# The server listens for queries on port 5300 (or PORT).
|
||||
#
|
||||
# The server listens for control connections on port 5301 (or EXTRAPORT1).
|
||||
#
|
||||
# A control connection is a TCP stream of lines like
|
||||
#
|
||||
# /pattern/
|
||||
# name ttl type rdata
|
||||
# name ttl type rdata
|
||||
# ...
|
||||
# /pattern/
|
||||
# name ttl type rdata
|
||||
# name ttl type rdata
|
||||
# ...
|
||||
#
|
||||
# There can be any number of patterns, each associated
|
||||
# with any number of response RRs. Each pattern is a
|
||||
# Perl regular expression. If an empty pattern ("//") is
|
||||
# received, the server will ignore all incoming queries (TCP
|
||||
# connections will still be accepted, but both UDP queries
|
||||
# and TCP queries will not be responded to). If a non-empty
|
||||
# pattern is then received over the same control connection,
|
||||
# default behavior is restored.
|
||||
#
|
||||
# Each incoming query is converted into a string of the form
|
||||
# "qname qtype" (the printable query domain name, space,
|
||||
# printable query type) and matched against each pattern.
|
||||
#
|
||||
# The first pattern matching the query is selected, and
|
||||
# the RR following the pattern line are sent in the
|
||||
# answer section of the response.
|
||||
#
|
||||
# Each new control connection causes the current set of
|
||||
# patterns and responses to be cleared before adding new
|
||||
# ones.
|
||||
#
|
||||
# The server handles UDP and TCP queries. Zone transfer
|
||||
# responses work, but must fit in a single 64 k message.
|
||||
#
|
||||
# Now you can add TSIG, just specify key/key data with:
|
||||
#
|
||||
# /pattern <key> <key_data>/
|
||||
# name ttl type rdata
|
||||
# name ttl type rdata
|
||||
#
|
||||
# Note that this data will still be sent with any request for
|
||||
# pattern, only this data will be signed. Currently, this is only
|
||||
# done for TCP.
|
||||
#
|
||||
# /pattern NOTIMP <key> <key_data>/
|
||||
# /pattern NOTIMP/
|
||||
#
|
||||
# Return a NOTIMP response
|
||||
#
|
||||
# /pattern EDNS=NOTIMP <key> <key_data>/
|
||||
# /pattern EDNS=NOTIMP/
|
||||
#
|
||||
# Return a NOTIMP response to an EDNS request
|
||||
#
|
||||
# /pattern EDNS=FORMERR <key> <key_data>/
|
||||
# /pattern EDNS=FORMERR/
|
||||
#
|
||||
# Return a FORMERR response to an EDNS request
|
||||
#
|
||||
# /pattern bad-id <key> <key_data>/
|
||||
# /pattern bad-id/
|
||||
#
|
||||
# will add 50 to the message id of the response.
|
||||
|
||||
|
||||
use IO::File;
|
||||
use IO::Socket;
|
||||
use Data::Dumper;
|
||||
use Net::DNS;
|
||||
use Net::DNS::Packet;
|
||||
use strict;
|
||||
|
||||
# Ignore SIGPIPE so we won't fail if peer closes a TCP socket early
|
||||
local $SIG{PIPE} = 'IGNORE';
|
||||
|
||||
# Flush logged output after every line
|
||||
local $| = 1;
|
||||
|
||||
# We default to listening on 10.53.0.2 for historical reasons
|
||||
# XXX: we should also be able to specify IPv6
|
||||
my $server_addr = "10.53.0.2";
|
||||
if (@ARGV > 0) {
|
||||
$server_addr = @ARGV[0];
|
||||
}
|
||||
|
||||
my $mainport = int($ENV{'PORT'});
|
||||
if (!$mainport) { $mainport = 5300; }
|
||||
my $ctrlport = int($ENV{'EXTRAPORT1'});
|
||||
if (!$ctrlport) { $ctrlport = 5301; }
|
||||
my $hmac_algorithm = $ENV{'DEFAULT_HMAC'};
|
||||
if (!defined($hmac_algorithm)) { $hmac_algorithm = "hmac-sha256"; }
|
||||
|
||||
# XXX: we should also be able to set the port numbers to listen on.
|
||||
my $ctlsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
|
||||
LocalPort => $ctrlport, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!";
|
||||
|
||||
my $udpsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
|
||||
LocalPort => $mainport, Proto => "udp", Reuse => 1) or die "$!";
|
||||
|
||||
my $tcpsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
|
||||
LocalPort => $mainport, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!";
|
||||
|
||||
print "listening on $server_addr:$mainport,$ctrlport.\n";
|
||||
print "Using Net::DNS $Net::DNS::VERSION\n";
|
||||
|
||||
my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!";
|
||||
print $pidf "$$\n" or die "cannot write pid file: $!";
|
||||
$pidf->close or die "cannot close pid file: $!";;
|
||||
sub rmpid { unlink "ans.pid"; exit 1; };
|
||||
|
||||
$SIG{INT} = \&rmpid;
|
||||
$SIG{TERM} = \&rmpid;
|
||||
|
||||
#my @answers = ();
|
||||
my @rules;
|
||||
my $udphandler;
|
||||
my $tcphandler;
|
||||
|
||||
sub handleUDP {
|
||||
my ($buf) = @_;
|
||||
my $request;
|
||||
|
||||
if ($Net::DNS::VERSION > 0.68) {
|
||||
$request = new Net::DNS::Packet(\$buf, 0);
|
||||
$@ and die $@;
|
||||
} else {
|
||||
my $err;
|
||||
($request, $err) = new Net::DNS::Packet(\$buf, 0);
|
||||
$err and die $err;
|
||||
}
|
||||
|
||||
my @questions = $request->question;
|
||||
my $qname = $questions[0]->qname;
|
||||
my $qtype = $questions[0]->qtype;
|
||||
my $qclass = $questions[0]->qclass;
|
||||
my $id = $request->header->id;
|
||||
|
||||
my $packet = new Net::DNS::Packet($qname, $qtype, $qclass);
|
||||
$packet->header->qr(1);
|
||||
$packet->header->aa(1);
|
||||
$packet->header->id($id);
|
||||
|
||||
# get the existing signature if any, and clear the additional section
|
||||
my $prev_tsig;
|
||||
while (my $rr = $request->pop("additional")) {
|
||||
$prev_tsig = $rr if ($rr->type eq "TSIG");
|
||||
}
|
||||
|
||||
my $r;
|
||||
foreach $r (@rules) {
|
||||
my $pattern = $r->{pattern};
|
||||
my($dbtype, $key_name, $key_data) = split(/ /,$pattern);
|
||||
print "[handleUDP] $dbtype, $key_name, $key_data\n";
|
||||
if ("$qname $qtype" =~ /$dbtype/) {
|
||||
my $a;
|
||||
foreach $a (@{$r->{answer}}) {
|
||||
$packet->push("answer", $a);
|
||||
}
|
||||
if (defined($key_name) && defined($key_data)) {
|
||||
my $tsig;
|
||||
# Sign the packet
|
||||
print " Signing the response with " .
|
||||
"$key_name/$key_data\n";
|
||||
|
||||
if ($Net::DNS::VERSION < 0.69) {
|
||||
$tsig = Net::DNS::RR->new(
|
||||
"$key_name TSIG $key_data");
|
||||
} else {
|
||||
$tsig = Net::DNS::RR->new(
|
||||
name => $key_name,
|
||||
algorithm => $hmac_algorithm,
|
||||
type => 'TSIG',
|
||||
key => $key_data);
|
||||
}
|
||||
|
||||
# These kluges are necessary because Net::DNS
|
||||
# doesn't know how to sign responses. We
|
||||
# clear compnames so that the TSIG key and
|
||||
# algorithm name won't be compressed, and
|
||||
# add one to arcount because the signing
|
||||
# function will attempt to decrement it,
|
||||
# which is incorrect in a response. Finally
|
||||
# we set request_mac to the previous digest.
|
||||
$packet->{"compnames"} = {}
|
||||
if ($Net::DNS::VERSION < 0.70);
|
||||
$packet->{"header"}{"arcount"} += 1
|
||||
if ($Net::DNS::VERSION < 0.70);
|
||||
if (defined($prev_tsig)) {
|
||||
if ($Net::DNS::VERSION < 0.73) {
|
||||
my $rmac = pack('n H*',
|
||||
length($prev_tsig->mac)/2,
|
||||
$prev_tsig->mac);
|
||||
$tsig->{"request_mac"} =
|
||||
unpack("H*", $rmac);
|
||||
} else {
|
||||
$tsig->request_mac(
|
||||
$prev_tsig->mac);
|
||||
}
|
||||
}
|
||||
|
||||
$packet->sign_tsig($tsig);
|
||||
}
|
||||
last;
|
||||
}
|
||||
}
|
||||
#$packet->print;
|
||||
|
||||
return $packet->data;
|
||||
}
|
||||
|
||||
# namelen:
|
||||
# given a stream of data, reads a DNS-formatted name and returns its
|
||||
# total length, thus making it possible to skip past it.
|
||||
sub namelen {
|
||||
my ($data) = @_;
|
||||
my $len = 0;
|
||||
my $label_len = 0;
|
||||
do {
|
||||
$label_len = unpack("c", $data);
|
||||
$data = substr($data, $label_len + 1);
|
||||
$len += $label_len + 1;
|
||||
} while ($label_len != 0);
|
||||
return ($len);
|
||||
}
|
||||
|
||||
# packetlen:
|
||||
# given a stream of data, reads a DNS wire-format packet and returns
|
||||
# its total length, making it possible to skip past it.
|
||||
sub packetlen {
|
||||
my ($data) = @_;
|
||||
my $q;
|
||||
my $rr;
|
||||
my $header;
|
||||
my $offset;
|
||||
|
||||
#
|
||||
# decode/encode were introduced in Net::DNS 0.68
|
||||
# parse is no longer a method and calling it here makes perl croak.
|
||||
#
|
||||
my $decode = 0;
|
||||
$decode = 1 if ($Net::DNS::VERSION >= 0.68);
|
||||
|
||||
if ($decode) {
|
||||
($header, $offset) = Net::DNS::Header->decode(\$data);
|
||||
} else {
|
||||
($header, $offset) = Net::DNS::Header->parse(\$data);
|
||||
}
|
||||
|
||||
for (1 .. $header->qdcount) {
|
||||
if ($decode) {
|
||||
($q, $offset) =
|
||||
Net::DNS::Question->decode(\$data, $offset);
|
||||
} else {
|
||||
($q, $offset) =
|
||||
Net::DNS::Question->parse(\$data, $offset);
|
||||
}
|
||||
}
|
||||
for (1 .. $header->ancount) {
|
||||
if ($decode) {
|
||||
($q, $offset) = Net::DNS::RR->decode(\$data, $offset);
|
||||
} else {
|
||||
($q, $offset) = Net::DNS::RR->parse(\$data, $offset);
|
||||
}
|
||||
}
|
||||
for (1 .. $header->nscount) {
|
||||
if ($decode) {
|
||||
($q, $offset) = Net::DNS::RR->decode(\$data, $offset);
|
||||
} else {
|
||||
($q, $offset) = Net::DNS::RR->parse(\$data, $offset);
|
||||
}
|
||||
}
|
||||
for (1 .. $header->arcount) {
|
||||
if ($decode) {
|
||||
($q, $offset) = Net::DNS::RR->decode(\$data, $offset);
|
||||
} else {
|
||||
($q, $offset) = Net::DNS::RR->parse(\$data, $offset);
|
||||
}
|
||||
}
|
||||
return $offset;
|
||||
}
|
||||
|
||||
# sign_tcp_continuation:
|
||||
# This is a hack to correct the problem that Net::DNS has no idea how
|
||||
# to sign multiple-message TCP responses. Several data that are included
|
||||
# in the digest when signing a query or the first message of a response are
|
||||
# omitted when signing subsequent messages in a TCP stream.
|
||||
#
|
||||
# Net::DNS::Packet->sign_tsig() has the ability to use a custom signing
|
||||
# function (specified by calling Packet->sign_func()). We use this
|
||||
# function as the signing function for TCP continuations, and it removes
|
||||
# the unwanted data from the digest before calling the default sign_hmac
|
||||
# function.
|
||||
sub sign_tcp_continuation {
|
||||
my ($key, $data) = @_;
|
||||
|
||||
# copy out first two bytes: size of the previous MAC
|
||||
my $rmacsize = unpack("n", $data);
|
||||
$data = substr($data, 2);
|
||||
|
||||
# copy out previous MAC
|
||||
my $rmac = substr($data, 0, $rmacsize);
|
||||
$data = substr($data, $rmacsize);
|
||||
|
||||
# try parsing out the packet information
|
||||
my $plen = packetlen($data);
|
||||
my $pdata = substr($data, 0, $plen);
|
||||
$data = substr($data, $plen);
|
||||
|
||||
# remove the keyname, ttl, class, and algorithm name
|
||||
$data = substr($data, namelen($data));
|
||||
$data = substr($data, 6);
|
||||
$data = substr($data, namelen($data));
|
||||
|
||||
# preserve the TSIG data
|
||||
my $tdata = substr($data, 0, 8);
|
||||
|
||||
# prepare a new digest and sign with it
|
||||
$data = pack("n", $rmacsize) . $rmac . $pdata . $tdata;
|
||||
return Net::DNS::RR::TSIG::sign_hmac($key, $data);
|
||||
}
|
||||
|
||||
sub handleTCP {
|
||||
my ($buf) = @_;
|
||||
my $request;
|
||||
|
||||
if ($Net::DNS::VERSION > 0.68) {
|
||||
$request = new Net::DNS::Packet(\$buf, 0);
|
||||
$@ and die $@;
|
||||
} else {
|
||||
my $err;
|
||||
($request, $err) = new Net::DNS::Packet(\$buf, 0);
|
||||
$err and die $err;
|
||||
}
|
||||
|
||||
my @questions = $request->question;
|
||||
my $qname = $questions[0]->qname;
|
||||
my $qtype = $questions[0]->qtype;
|
||||
my $qclass = $questions[0]->qclass;
|
||||
my $id = $request->header->id;
|
||||
my @additional = $request->additional;
|
||||
my $has_opt = 0;
|
||||
foreach (@additional) {
|
||||
$has_opt = 1 if (ref($_) eq 'Net::DNS::RR::OPT');
|
||||
}
|
||||
|
||||
my $opaque;
|
||||
|
||||
my $packet = new Net::DNS::Packet($qname, $qtype, $qclass);
|
||||
$packet->header->qr(1);
|
||||
$packet->header->aa(1);
|
||||
$packet->header->id($id);
|
||||
|
||||
# get the existing signature if any, and clear the additional section
|
||||
my $prev_tsig;
|
||||
my $signer;
|
||||
my $continuation = 0;
|
||||
if ($Net::DNS::VERSION < 0.81) {
|
||||
while (my $rr = $request->pop("additional")) {
|
||||
if ($rr->type eq "TSIG") {
|
||||
$prev_tsig = $rr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my @results = ();
|
||||
my $count_these = 0;
|
||||
|
||||
my $r;
|
||||
foreach $r (@rules) {
|
||||
my $pattern = $r->{pattern};
|
||||
my($dbtype, $key_name, $key_data, $tname) = split(/ /,$pattern);
|
||||
print "[handleTCP] $dbtype, $key_name, $key_data, $tname \n";
|
||||
if ("$qname $qtype" =~ /$dbtype/) {
|
||||
$count_these++;
|
||||
my $a;
|
||||
my $done = 0;
|
||||
|
||||
while (defined($key_name) &&
|
||||
($key_name eq "NOTIMP" || $key_name eq "EDNS=NOTIMP" ||
|
||||
$key_name eq "EDNS=FORMERR" || $key_name eq "bad-id")) {
|
||||
|
||||
if (defined($key_name) && $key_name eq "NOTIMP") {
|
||||
$packet->header->rcode('NOTIMP') if (!$done);
|
||||
$key_name = $key_data;
|
||||
($key_data, $tname) = split(/ /,$tname);
|
||||
$done = 1;
|
||||
}
|
||||
|
||||
if (defined($key_name) && $key_name eq "EDNS=NOTIMP") {
|
||||
if ($has_opt) {
|
||||
$packet->header->rcode('NOTIMP') if (!$done);
|
||||
$done = 1;
|
||||
}
|
||||
$key_name = $key_data;
|
||||
($key_data, $tname) = split(/ /,$tname);
|
||||
}
|
||||
|
||||
if (defined($key_name) && $key_name eq "EDNS=FORMERR") {
|
||||
if ($has_opt) {
|
||||
$packet->header->rcode('FORMERR') if (!$done);
|
||||
$done = 1;
|
||||
}
|
||||
$key_name = $key_data;
|
||||
($key_data, $tname) = split(/ /,$tname);
|
||||
}
|
||||
|
||||
if (defined($key_name) && $key_name eq "bad-id") {
|
||||
$packet->header->id(($id+50)%0xffff);
|
||||
$key_name = $key_data;
|
||||
($key_data, $tname) = split(/ /,$tname);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$done) {
|
||||
foreach $a (@{$r->{answer}}) {
|
||||
$packet->push("answer", $a);
|
||||
}
|
||||
}
|
||||
|
||||
if (defined($key_name) && defined($key_data)) {
|
||||
my $tsig;
|
||||
# sign the packet
|
||||
print " Signing the data with " .
|
||||
"$key_name/$key_data\n";
|
||||
|
||||
if ($Net::DNS::VERSION < 0.69) {
|
||||
$tsig = Net::DNS::RR->new(
|
||||
"$key_name TSIG $key_data");
|
||||
$tsig->algorithm = $hmac_algorithm;
|
||||
} elsif ($Net::DNS::VERSION >= 0.81 &&
|
||||
$continuation) {
|
||||
} elsif ($Net::DNS::VERSION >= 0.75 &&
|
||||
$continuation) {
|
||||
$tsig = $prev_tsig;
|
||||
} else {
|
||||
$tsig = Net::DNS::RR->new(
|
||||
name => $key_name,
|
||||
algorithm => $hmac_algorithm,
|
||||
type => 'TSIG',
|
||||
key => $key_data);
|
||||
}
|
||||
|
||||
# These kluges are necessary because Net::DNS
|
||||
# doesn't know how to sign responses. We
|
||||
# clear compnames so that the TSIG key and
|
||||
# algorithm name won't be compressed, and
|
||||
# add one to arcount because the signing
|
||||
# function will attempt to decrement it,
|
||||
# which is incorrect in a response. Finally
|
||||
# we set request_mac to the previous digest.
|
||||
$packet->{"compnames"} = {}
|
||||
if ($Net::DNS::VERSION < 0.70);
|
||||
$packet->{"header"}{"arcount"} += 1
|
||||
if ($Net::DNS::VERSION < 0.70);
|
||||
if (defined($prev_tsig)) {
|
||||
if ($Net::DNS::VERSION < 0.73) {
|
||||
my $rmac = pack('n H*',
|
||||
length($prev_tsig->mac)/2,
|
||||
$prev_tsig->mac);
|
||||
$tsig->{"request_mac"} =
|
||||
unpack("H*", $rmac);
|
||||
} elsif ($Net::DNS::VERSION < 0.81) {
|
||||
$tsig->request_mac(
|
||||
$prev_tsig->mac);
|
||||
}
|
||||
}
|
||||
|
||||
$tsig->sign_func($signer) if defined($signer);
|
||||
$tsig->continuation($continuation) if
|
||||
($Net::DNS::VERSION >= 0.71 &&
|
||||
$Net::DNS::VERSION <= 0.74 );
|
||||
if ($Net::DNS::VERSION < 0.81) {
|
||||
$packet->sign_tsig($tsig);
|
||||
} elsif ($continuation) {
|
||||
$opaque = $packet->sign_tsig($opaque);
|
||||
} else {
|
||||
$opaque = $packet->sign_tsig($request);
|
||||
}
|
||||
$signer = \&sign_tcp_continuation
|
||||
if ($Net::DNS::VERSION < 0.70);
|
||||
$continuation = 1;
|
||||
|
||||
my $copy =
|
||||
Net::DNS::Packet->new(\($packet->data));
|
||||
$prev_tsig = $copy->pop("additional");
|
||||
}
|
||||
#$packet->print;
|
||||
push(@results,$packet->data);
|
||||
last if ($done);
|
||||
if ($tname eq "") {
|
||||
$tname = $qname;
|
||||
}
|
||||
$packet = new Net::DNS::Packet($tname, $qtype, $qclass);
|
||||
$packet->header->qr(1);
|
||||
$packet->header->aa(1);
|
||||
$packet->header->id($id);
|
||||
}
|
||||
}
|
||||
print " A total of $count_these patterns matched\n";
|
||||
return \@results;
|
||||
}
|
||||
|
||||
# Main
|
||||
my $rin;
|
||||
my $rout;
|
||||
for (;;) {
|
||||
$rin = '';
|
||||
vec($rin, fileno($ctlsock), 1) = 1;
|
||||
vec($rin, fileno($tcpsock), 1) = 1;
|
||||
vec($rin, fileno($udpsock), 1) = 1;
|
||||
|
||||
select($rout = $rin, undef, undef, undef);
|
||||
|
||||
if (vec($rout, fileno($ctlsock), 1)) {
|
||||
warn "ctl conn";
|
||||
my $conn = $ctlsock->accept;
|
||||
my $rule = ();
|
||||
@rules = ();
|
||||
while (my $line = $conn->getline) {
|
||||
chomp $line;
|
||||
if ($line =~ m!^/(.*)/$!) {
|
||||
if (length($1) == 0) {
|
||||
$udphandler = sub { return; };
|
||||
$tcphandler = sub { return; };
|
||||
} else {
|
||||
$udphandler = \&handleUDP;
|
||||
$tcphandler = \&handleTCP;
|
||||
$rule = { pattern => $1, answer => [] };
|
||||
push(@rules, $rule);
|
||||
}
|
||||
} else {
|
||||
push(@{$rule->{answer}},
|
||||
new Net::DNS::RR($line));
|
||||
}
|
||||
}
|
||||
$conn->close;
|
||||
#print Dumper(@rules);
|
||||
#print "+=+=+ $rules[0]->{'pattern'}\n";
|
||||
#print "+=+=+ $rules[0]->{'answer'}->[0]->{'rname'}\n";
|
||||
#print "+=+=+ $rules[0]->{'answer'}->[0]\n";
|
||||
} elsif (vec($rout, fileno($udpsock), 1)) {
|
||||
printf "UDP request\n";
|
||||
my $buf;
|
||||
$udpsock->recv($buf, 512);
|
||||
my $result = &$udphandler($buf);
|
||||
if (defined($result)) {
|
||||
my $num_chars = $udpsock->send($result);
|
||||
print " Sent $num_chars bytes via UDP\n";
|
||||
}
|
||||
} elsif (vec($rout, fileno($tcpsock), 1)) {
|
||||
my $conn = $tcpsock->accept;
|
||||
my $buf;
|
||||
for (;;) {
|
||||
my $lenbuf;
|
||||
my $n = $conn->sysread($lenbuf, 2);
|
||||
last unless $n == 2;
|
||||
my $len = unpack("n", $lenbuf);
|
||||
$n = $conn->sysread($buf, $len);
|
||||
last unless $n == $len;
|
||||
print "TCP request\n";
|
||||
my $result = &$tcphandler($buf);
|
||||
if (defined($result)) {
|
||||
foreach my $response (@$result) {
|
||||
$len = length($response);
|
||||
$n = $conn->syswrite(pack("n", $len), 2);
|
||||
$n = $conn->syswrite($response, $len);
|
||||
print " Sent: $n chars via TCP\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
$conn->close;
|
||||
}
|
||||
}
|
||||
|
|
@ -329,7 +329,7 @@ sub construct_ans_command {
|
|||
} elsif (-e "$testdir/$server/ans.pl") {
|
||||
$command = "$PERL ans.pl";
|
||||
} else {
|
||||
$command = "$PERL $srcdir/ans.pl 10.53.0.$n";
|
||||
die "unable to find ans.pl or ans.py in \"$testdir/$server\"\n";
|
||||
}
|
||||
|
||||
if ($options) {
|
||||
|
|
|
|||
433
bin/tests/system/xfer/ans5/ans.py
Normal file
433
bin/tests/system/xfer/ans5/ans.py
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
from collections.abc import AsyncGenerator, Collection
|
||||
from typing import final
|
||||
|
||||
import abc
|
||||
|
||||
import dns.message
|
||||
import dns.name
|
||||
import dns.rcode
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
import dns.rrset
|
||||
import dns.tsig
|
||||
|
||||
from isctest.asyncserver import (
|
||||
AxfrHandler,
|
||||
ControllableAsyncDnsServer,
|
||||
DnsProtocol,
|
||||
DnsResponseSend,
|
||||
QueryContext,
|
||||
ResponseAction,
|
||||
ResponseHandler,
|
||||
SwitchControlCommand,
|
||||
)
|
||||
from isctest.vars.algorithms import ALG_VARS
|
||||
|
||||
GOOD_KEY_DATA = "LSAnCU+Z"
|
||||
DEFAULT_KEY = dns.tsig.Key("tsig_key", GOOD_KEY_DATA, ALG_VARS["DEFAULT_HMAC"])
|
||||
BAD_KEY = dns.tsig.Key("bad_key", GOOD_KEY_DATA, ALG_VARS["DEFAULT_HMAC"])
|
||||
UNUSED_KEY = dns.tsig.Key("unused_key", GOOD_KEY_DATA, ALG_VARS["DEFAULT_HMAC"])
|
||||
KEYRING = {key.name: key for key in (DEFAULT_KEY, BAD_KEY, UNUSED_KEY)}
|
||||
|
||||
KEY_WITH_BAD_DATA = dns.tsig.Key("tsig_key", "abcd1234ffff", ALG_VARS["DEFAULT_HMAC"])
|
||||
|
||||
|
||||
class ResponseHandlerWrapper(ResponseHandler, abc.ABC):
|
||||
def __init__(self, inner: ResponseHandler) -> None:
|
||||
self._inner = inner
|
||||
|
||||
def match(self, qctx: QueryContext) -> bool:
|
||||
return self._inner.match(qctx)
|
||||
|
||||
def _on_query_received(self, qctx: QueryContext) -> None:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _modify_response(
|
||||
self, qctx: QueryContext, response_action: ResponseAction
|
||||
) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
@final
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[ResponseAction, None]:
|
||||
self._on_query_received(qctx)
|
||||
async for response_action in self._inner.get_responses(qctx):
|
||||
self._modify_response(qctx, response_action)
|
||||
yield response_action
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.__class__.__name__}({self._inner})"
|
||||
|
||||
|
||||
class SignResponses(ResponseHandlerWrapper):
|
||||
"""
|
||||
This handler encapsulates another handler and signs all responses it yields
|
||||
with TSIG using the specified key.
|
||||
|
||||
If the query is over TCP, it maintains the TSIG context across multiple
|
||||
messages to allow proper signing of multi-message responses.
|
||||
|
||||
Ideally, TSIG context would be handled in isctest.asyncserver, but that would
|
||||
require more extensive changes there, so it is implemented here for a single
|
||||
test.
|
||||
"""
|
||||
|
||||
def __init__(self, inner: ResponseHandler, key: dns.tsig.Key = DEFAULT_KEY) -> None:
|
||||
super().__init__(inner)
|
||||
self._key = key
|
||||
self._tsig_ctx: dns.tsig.GSSTSig | dns.tsig.HMACTSig | None = None
|
||||
|
||||
def _on_query_received(self, qctx: QueryContext) -> None:
|
||||
self._tsig_ctx = None
|
||||
|
||||
def _apply_tsig_context(self, response: dns.message.Message) -> None:
|
||||
# On TCP we need to maintain the TSIG context across multiple messages.
|
||||
# Force TSIG materialization to get the updated context by calling to_wire().
|
||||
_ = response.to_wire(multi=True, tsig_ctx=self._tsig_ctx)
|
||||
# Cache TSIG context for the next message.
|
||||
self._tsig_ctx = response.tsig_ctx
|
||||
|
||||
def _modify_response(
|
||||
self, qctx: QueryContext, response_action: ResponseAction
|
||||
) -> None:
|
||||
assert isinstance(
|
||||
response_action, DnsResponseSend
|
||||
), "SignResponses can only wrap handlers that yield DnsResponseSend"
|
||||
response_action.response.use_tsig(self._key)
|
||||
if qctx.protocol == DnsProtocol.TCP:
|
||||
self._apply_tsig_context(response_action.response)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"SignResponses({self._inner}, key={self._key})"
|
||||
|
||||
|
||||
class SignFirstResponse(ResponseHandlerWrapper):
|
||||
def __init__(self, inner: ResponseHandler, key: dns.tsig.Key = DEFAULT_KEY) -> None:
|
||||
super().__init__(inner)
|
||||
self._key = key
|
||||
self._first_yielded = False
|
||||
|
||||
def _on_query_received(self, qctx: QueryContext) -> None:
|
||||
self._first_yielded = False
|
||||
|
||||
def _modify_response(
|
||||
self, qctx: QueryContext, response_action: ResponseAction
|
||||
) -> None:
|
||||
assert isinstance(
|
||||
response_action, DnsResponseSend
|
||||
), "SignFirstResponse can only wrap handlers that yield DnsResponseSend"
|
||||
if not self._first_yielded:
|
||||
response_action.response.use_tsig(self._key)
|
||||
self._first_yielded = True
|
||||
else:
|
||||
response_action.response.tsig = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"SignFirstResponse({self._inner}, key={self._key})"
|
||||
|
||||
|
||||
class Add50ToMessageIdFromSecondResponse(ResponseHandlerWrapper):
|
||||
def __init__(self, inner: ResponseHandler) -> None:
|
||||
super().__init__(inner)
|
||||
self._first_yielded = False
|
||||
|
||||
def _on_query_received(self, qctx: QueryContext) -> None:
|
||||
self._first_yielded = False
|
||||
|
||||
def _modify_response(
|
||||
self, qctx: QueryContext, response_action: ResponseAction
|
||||
) -> None:
|
||||
if self._first_yielded:
|
||||
assert isinstance(
|
||||
response_action, DnsResponseSend
|
||||
), "Add50ToMessageIdFromSecondResponse can only wrap handlers that yield DnsResponseSend from the second response onward"
|
||||
response_action.response.id += 50
|
||||
else:
|
||||
self._first_yielded = True
|
||||
|
||||
|
||||
class ClearTsig(ResponseHandlerWrapper):
|
||||
def _modify_response(
|
||||
self, qctx: QueryContext, response_action: ResponseAction
|
||||
) -> None:
|
||||
assert isinstance(
|
||||
response_action, DnsResponseSend
|
||||
), "ClearTsig can only wrap handlers that yield DnsResponseSend"
|
||||
response_action.response.tsig = None
|
||||
|
||||
|
||||
def rrset(
|
||||
owner: str | dns.name.Name,
|
||||
ttl: int,
|
||||
rdtype: dns.rdatatype.RdataType,
|
||||
rdata: str,
|
||||
) -> dns.rrset.RRset:
|
||||
return dns.rrset.from_text(
|
||||
owner,
|
||||
ttl,
|
||||
dns.rdataclass.IN,
|
||||
rdtype,
|
||||
rdata,
|
||||
)
|
||||
|
||||
|
||||
def soa(
|
||||
serial: int,
|
||||
*,
|
||||
owner: str = "nil.",
|
||||
mname: str = "ns.nil.",
|
||||
rname: str = "root.nil.",
|
||||
) -> dns.rrset.RRset:
|
||||
return rrset(
|
||||
owner,
|
||||
300,
|
||||
dns.rdatatype.SOA,
|
||||
f"{mname} {rname} {serial} 300 300 604800 300",
|
||||
)
|
||||
|
||||
|
||||
class SoaHandler(ResponseHandler):
|
||||
def __init__(self, serial: int = 1) -> None:
|
||||
self._serial = serial
|
||||
|
||||
def match(self, qctx: QueryContext) -> bool:
|
||||
return qctx.qtype == dns.rdatatype.SOA
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
qctx.response.answer.append(soa(self._serial))
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
def ns() -> dns.rrset.RRset:
|
||||
return rrset(
|
||||
"nil.",
|
||||
300,
|
||||
dns.rdatatype.NS,
|
||||
"ns.nil.",
|
||||
)
|
||||
|
||||
|
||||
def txt(data: str) -> dns.rrset.RRset:
|
||||
return rrset(
|
||||
"nil.",
|
||||
300,
|
||||
dns.rdatatype.TXT,
|
||||
f'"{data}"',
|
||||
)
|
||||
|
||||
|
||||
def a() -> dns.rrset.RRset:
|
||||
return rrset(
|
||||
"a.nil.",
|
||||
60,
|
||||
dns.rdatatype.A,
|
||||
"10.0.0.61",
|
||||
)
|
||||
|
||||
|
||||
def extra_a() -> dns.rrset.RRset:
|
||||
return rrset(
|
||||
"b.nil.",
|
||||
60,
|
||||
dns.rdatatype.A,
|
||||
"10.0.0.62",
|
||||
)
|
||||
|
||||
|
||||
class XferAxfrHandler(AxfrHandler):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
txt_data: str,
|
||||
soa_serial: int = 1,
|
||||
extra_a_record: bool = False,
|
||||
final_soa_mismatch: bool = False,
|
||||
) -> None:
|
||||
self._txt_data = txt_data
|
||||
self._soa_serial = soa_serial
|
||||
self._extra_a_record = extra_a_record
|
||||
self._final_soa_mismatch = final_soa_mismatch
|
||||
|
||||
@property
|
||||
def initial_soa(self) -> dns.rrset.RRset:
|
||||
return soa(self._soa_serial)
|
||||
|
||||
@property
|
||||
def zone_contents(self) -> Collection[dns.rrset.RRset]:
|
||||
records = [ns(), txt(self._txt_data), a()]
|
||||
if self._extra_a_record:
|
||||
records.append(extra_a())
|
||||
return records
|
||||
|
||||
@property
|
||||
def final_soa(self) -> dns.rrset.RRset:
|
||||
if self._final_soa_mismatch:
|
||||
return soa(self._soa_serial, mname="whatever.", rname="other.")
|
||||
return soa(self._soa_serial)
|
||||
|
||||
|
||||
class WrongQnameInFinalSoa(ResponseHandlerWrapper):
|
||||
def __init__(self, inner: XferAxfrHandler) -> None:
|
||||
super().__init__(inner)
|
||||
self._messages_until_final_soa = 2
|
||||
|
||||
def _modify_response(
|
||||
self, qctx: QueryContext, response_action: ResponseAction
|
||||
) -> None:
|
||||
if self._messages_until_final_soa == 0:
|
||||
assert isinstance(
|
||||
response_action, DnsResponseSend
|
||||
), "WrongQnameInFinalSoaAxfrHandler can only wrap handlers that yield DnsResponseSend from the final SOA response"
|
||||
response_action.response.question[0].name = dns.name.from_text("ns.wrong.")
|
||||
self._messages_until_final_soa -= 1
|
||||
|
||||
|
||||
class IxfrNotimpHandler(ResponseHandler):
|
||||
def match(self, qctx: QueryContext) -> bool:
|
||||
return qctx.qtype == dns.rdatatype.IXFR
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
qctx.response.set_rcode(dns.rcode.NOTIMP)
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
class AxfrEdnsRcodeHandler(ResponseHandler):
|
||||
def __init__(self, rcode: dns.rcode.Rcode) -> None:
|
||||
self._rcode = rcode
|
||||
|
||||
def match(self, qctx: QueryContext) -> bool:
|
||||
return qctx.qtype == dns.rdatatype.AXFR and qctx.query.edns > -1
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
qctx.response.set_rcode(self._rcode)
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
server = ControllableAsyncDnsServer(
|
||||
default_aa=True, default_rcode=dns.rcode.NOERROR, keyring=KEYRING
|
||||
)
|
||||
switch_command = SwitchControlCommand(
|
||||
{
|
||||
"badkeydata": (
|
||||
SignResponses(SoaHandler(serial := 3)),
|
||||
SignResponses(
|
||||
XferAxfrHandler(soa_serial=serial, txt_data="bad keydata AXFR"),
|
||||
KEY_WITH_BAD_DATA,
|
||||
),
|
||||
),
|
||||
"badmessageid": (
|
||||
SignResponses(SoaHandler()),
|
||||
SignResponses(
|
||||
Add50ToMessageIdFromSecondResponse(
|
||||
XferAxfrHandler(txt_data="bad message id")
|
||||
),
|
||||
),
|
||||
),
|
||||
"ednsformerr": (
|
||||
SignResponses(SoaHandler()),
|
||||
SignResponses(AxfrEdnsRcodeHandler(rcode=dns.rcode.FORMERR)),
|
||||
SignResponses(XferAxfrHandler(txt_data="EDNS FORMERR")),
|
||||
),
|
||||
"ednsnotimp": (
|
||||
SignResponses(SoaHandler()),
|
||||
SignResponses(AxfrEdnsRcodeHandler(rcode=dns.rcode.NOTIMP)),
|
||||
SignResponses(XferAxfrHandler(txt_data="EDNS NOTIMP")),
|
||||
),
|
||||
"goodaxfr": (
|
||||
SignResponses(SoaHandler()),
|
||||
SignResponses(XferAxfrHandler(txt_data="initial AXFR")),
|
||||
),
|
||||
"ixfrnotimp": (
|
||||
SignResponses(SoaHandler(serial := 2)),
|
||||
SignResponses(IxfrNotimpHandler()),
|
||||
SignResponses(
|
||||
XferAxfrHandler(soa_serial=serial, txt_data="IXFR NOTIMP")
|
||||
),
|
||||
),
|
||||
"partial": (
|
||||
SignResponses(SoaHandler(serial := 4)),
|
||||
SignFirstResponse(
|
||||
XferAxfrHandler(
|
||||
soa_serial=serial,
|
||||
txt_data="partially signed AXFR",
|
||||
extra_a_record=True,
|
||||
),
|
||||
),
|
||||
),
|
||||
"soamismatch": (
|
||||
SignResponses(SoaHandler()),
|
||||
SignResponses(
|
||||
XferAxfrHandler(
|
||||
txt_data="SOA mismatch AXFR",
|
||||
final_soa_mismatch=True,
|
||||
)
|
||||
),
|
||||
),
|
||||
"unknownkey": (
|
||||
SignResponses(SoaHandler(serial := 5), BAD_KEY),
|
||||
SignResponses(
|
||||
XferAxfrHandler(
|
||||
soa_serial=serial,
|
||||
txt_data="unknown key AXFR",
|
||||
extra_a_record=True,
|
||||
),
|
||||
BAD_KEY,
|
||||
),
|
||||
),
|
||||
"unsigned": (
|
||||
SignResponses(SoaHandler(serial := 2)),
|
||||
ClearTsig(
|
||||
XferAxfrHandler(
|
||||
soa_serial=serial,
|
||||
txt_data="unsigned AXFR",
|
||||
extra_a_record=True,
|
||||
)
|
||||
),
|
||||
),
|
||||
"wrongkey": (
|
||||
SignResponses(SoaHandler(serial := 6), UNUSED_KEY),
|
||||
SignResponses(
|
||||
XferAxfrHandler(
|
||||
soa_serial=serial,
|
||||
txt_data="incorrect key AXFR",
|
||||
extra_a_record=True,
|
||||
),
|
||||
UNUSED_KEY,
|
||||
),
|
||||
),
|
||||
"wrongname": (
|
||||
SignResponses(SoaHandler()),
|
||||
SignResponses(
|
||||
WrongQnameInFinalSoa(
|
||||
XferAxfrHandler(txt_data="wrong question AXFR")
|
||||
)
|
||||
),
|
||||
),
|
||||
}
|
||||
)
|
||||
server.install_control_command(switch_command)
|
||||
server.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
/SOA tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 3 300 300 604800 300
|
||||
/AXFR tsig_key abcd1234ffff/
|
||||
nil. 300 SOA ns.nil. root.nil. 3 300 300 604800 300
|
||||
/AXFR tsig_key abcd1234ffff/
|
||||
nil. 300 NS ns.nil.
|
||||
nil. 300 TXT "bad keydata AXFR"
|
||||
a.nil. 60 A 10.0.0.61
|
||||
/AXFR tsig_key abcd1234ffff/
|
||||
nil. 300 SOA ns.nil. root.nil. 3 300 300 604800 300
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
/SOA tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
/AXFR tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
/AXFR bad-id tsig_key LSAnCU+Z/
|
||||
nil. 300 NS ns.nil.
|
||||
nil. 300 TXT "bad message id"
|
||||
a.nil. 60 A 10.0.0.61
|
||||
/AXFR bad-id tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
/SOA tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
/AXFR EDNS=FORMERR tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
/AXFR EDNS=FORMERR tsig_key LSAnCU+Z/
|
||||
nil. 300 NS ns.nil.
|
||||
nil. 300 TXT "EDNS FORMERR"
|
||||
a.nil. 60 A 10.0.0.61
|
||||
/AXFR EDNS=FORMERR tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
/SOA tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
/AXFR EDNS=NOTIMP tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
/AXFR tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
/AXFR tsig_key LSAnCU+Z/
|
||||
nil. 300 NS ns.nil.
|
||||
nil. 300 TXT "EDNS NOTIMP"
|
||||
a.nil. 60 A 10.0.0.61
|
||||
/AXFR tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
/SOA tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
/AXFR tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
/AXFR tsig_key LSAnCU+Z/
|
||||
nil. 300 NS ns.nil.
|
||||
nil. 300 TXT "initial AXFR"
|
||||
a.nil. 60 A 10.0.0.61
|
||||
/AXFR tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
/SOA tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 2 300 300 604800 300
|
||||
/IXFR NOTIMP tsig_key LSAnCU+Z/
|
||||
/AXFR tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 2 300 300 604800 300
|
||||
/AXFR tsig_key LSAnCU+Z/
|
||||
nil. 300 NS ns.nil.
|
||||
nil. 300 TXT "IXFR NOTIMP"
|
||||
a.nil. 60 A 10.0.0.61
|
||||
/AXFR tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 2 300 300 604800 300
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
/SOA tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 4 300 300 604800 300
|
||||
/AXFR tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 4 300 300 604800 300
|
||||
/AXFR/
|
||||
nil. 300 NS ns.nil.
|
||||
nil. 300 TXT "partially signed AXFR"
|
||||
a.nil. 60 A 10.0.0.61
|
||||
b.nil. 60 A 10.0.0.62
|
||||
/AXFR/
|
||||
nil. 300 SOA ns.nil. root.nil. 4 300 300 604800 300
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
/SOA tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
/AXFR tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
/AXFR tsig_key LSAnCU+Z/
|
||||
nil. 300 NS ns.nil.
|
||||
nil. 300 TXT "SOA mismatch AXFR"
|
||||
a.nil. 60 A 10.0.0.61
|
||||
/AXFR tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA whatever. other. 1 300 300 604800 300
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
/SOA bad_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 5 300 300 604800 300
|
||||
/AXFR bad_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 5 300 300 604800 300
|
||||
/AXFR bad_key LSAnCU+Z/
|
||||
nil. 300 NS ns.nil.
|
||||
nil. 300 TXT "unknown key AXFR"
|
||||
a.nil. 60 A 10.0.0.61
|
||||
b.nil. 60 A 10.0.0.62
|
||||
/AXFR bad_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 5 300 300 604800 300
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
/SOA tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 2 300 300 604800 300
|
||||
/AXFR/
|
||||
nil. 300 SOA ns.nil. root.nil. 2 300 300 604800 300
|
||||
/AXFR/
|
||||
nil. 300 NS ns.nil.
|
||||
nil. 300 TXT "unsigned AXFR"
|
||||
a.nil. 60 A 10.0.0.61
|
||||
b.nil. 60 A 10.0.0.62
|
||||
/AXFR/
|
||||
nil. 300 SOA ns.nil. root.nil. 2 300 300 604800 300
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
/SOA unused_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 6 300 300 604800 300
|
||||
/AXFR unused_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 6 300 300 604800 300
|
||||
/AXFR unused_key LSAnCU+Z/
|
||||
nil. 300 NS ns.nil.
|
||||
nil. 300 TXT "incorrect key AXFR"
|
||||
a.nil. 60 A 10.0.0.61
|
||||
b.nil. 60 A 10.0.0.62
|
||||
/AXFR unused_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 6 300 300 604800 300
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
/SOA tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
/AXFR tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
/AXFR tsig_key LSAnCU+Z ns.wrong./
|
||||
nil. 300 NS ns.nil.
|
||||
nil. 300 TXT "wrong question AXFR"
|
||||
a.nil. 60 A 10.0.0.61
|
||||
/AXFR tsig_key LSAnCU+Z/
|
||||
nil. 300 SOA ns.nil. root.nil. 1 300 300 604800 300
|
||||
|
|
@ -14,7 +14,6 @@ from re import compile as Re
|
|||
|
||||
import fileinput
|
||||
import os
|
||||
import socket
|
||||
import time
|
||||
|
||||
import dns.message
|
||||
|
|
@ -33,23 +32,17 @@ NEW_SOA_SERIAL = 1397051953
|
|||
OLD_SOA_SERIAL = 1397051952
|
||||
|
||||
|
||||
def sendcmd(cmdfile):
|
||||
host = "10.53.0.5"
|
||||
port = int(isctest.vars.ALL["EXTRAPORT1"])
|
||||
cmdfile = f"ans5/{cmdfile}"
|
||||
assert os.path.exists(cmdfile)
|
||||
|
||||
sock = socket.create_connection((host, port))
|
||||
with open(cmdfile, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
sock.sendall(line.encode())
|
||||
sock.close()
|
||||
def send_switch_control_command(command):
|
||||
control_query = isctest.query.create(
|
||||
f"{command}.switch._control.", dns.rdatatype.TXT
|
||||
)
|
||||
isctest.query.tcp(control_query, "10.53.0.5")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
def after_servers_start(templates, ns4):
|
||||
# initial correctly-signed transfer should succeed
|
||||
sendcmd("goodaxfr")
|
||||
send_switch_control_command("goodaxfr")
|
||||
|
||||
with ns4.watch_log_from_here() as watcher:
|
||||
templates.render("ns4/named.conf", {"ns4_as_secondary_for_nil": True})
|
||||
|
|
@ -299,13 +292,13 @@ def test_make_ns4_secondary_for_nil():
|
|||
isctest.run.retry_with_timeout(_wait_for_soa, timeout=10)
|
||||
return True
|
||||
|
||||
sendcmd("goodaxfr")
|
||||
send_switch_control_command("goodaxfr")
|
||||
assert wait_for_soa(), "SOA not found in the response"
|
||||
check_rdata_in_txt_record("initial AXFR")
|
||||
|
||||
|
||||
def test_handle_ixfr_notimp(ns4):
|
||||
sendcmd("ixfrnotimp")
|
||||
send_switch_control_command("ixfrnotimp")
|
||||
with ns4.watch_log_from_here() as watcher_transfer_success:
|
||||
with ns4.watch_log_from_here() as watcher_requesting_ixfr:
|
||||
ns4.rndc("refresh nil.")
|
||||
|
|
@ -363,7 +356,7 @@ def test_handle_ixfr_notimp(ns4):
|
|||
],
|
||||
)
|
||||
def test_under_signed_transfer(command_file, expected_rdata, named_log_line, ns4):
|
||||
sendcmd(command_file)
|
||||
send_switch_control_command(command_file)
|
||||
with ns4.watch_log_from_here() as watcher:
|
||||
ns4.rndc("retransfer nil.")
|
||||
watcher.wait_for_line(named_log_line)
|
||||
|
|
@ -371,14 +364,14 @@ def test_under_signed_transfer(command_file, expected_rdata, named_log_line, ns4
|
|||
|
||||
|
||||
def test_handle_edns_notimp(ns4):
|
||||
sendcmd("ednsnotimp")
|
||||
send_switch_control_command("ednsnotimp")
|
||||
with ns4.watch_log_from_here() as watcher:
|
||||
ns4.rndc("retransfer nil.")
|
||||
watcher.wait_for_line("Transfer status: NOTIMP")
|
||||
|
||||
|
||||
def test_handle_edns_formerr(ns4):
|
||||
sendcmd("ednsformerr")
|
||||
send_switch_control_command("ednsformerr")
|
||||
with ns4.watch_log_from_here() as watcher:
|
||||
ns4.rndc("retransfer nil.")
|
||||
watcher.wait_for_line("Transfer status: success")
|
||||
|
|
|
|||
Loading…
Reference in a new issue