mirror of
https://github.com/isc-projects/bind9.git
synced 2026-05-28 04:34:54 -04:00
chg: test: Use isctest.asyncserver in the "resolver" system test
Depends on !11403, !11419 Merge branch 'stepan/resolver-asyncserver' into 'main' See merge request isc-projects/bind9!11411
This commit is contained in:
commit
11e6d1c0b9
11 changed files with 917 additions and 875 deletions
|
|
@ -642,6 +642,105 @@ class QnameHandler(ResponseHandler):
|
|||
return qctx.qname in self._qnames
|
||||
|
||||
|
||||
class QnameQtypeHandler(QnameHandler):
|
||||
"""
|
||||
Handle queries for which both of the following conditions are true:
|
||||
|
||||
- the query's QNAME is present in `self.qnames`,
|
||||
- the query's QTYPE is present in `self.qtypes`.
|
||||
"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def qtypes(self) -> List[dns.rdatatype.RdataType]:
|
||||
"""
|
||||
A list of QTYPEs handled by this class.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._qtypes: List[dns.rdatatype.RdataType] = self.qtypes
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.__class__.__name__}(QNAMEs: {', '.join(self.qnames)}; QTYPEs: {', '.join(map(str, self.qtypes))})"
|
||||
|
||||
def match(self, qctx: QueryContext) -> bool:
|
||||
"""
|
||||
Handle queries whose QNAME and QTYPE match any of the QNAMEs and
|
||||
QTYPEs handled by this class.
|
||||
"""
|
||||
return qctx.qtype in self._qtypes and super().match(qctx)
|
||||
|
||||
|
||||
class StaticResponseHandler(ResponseHandler):
|
||||
"""
|
||||
Base class used for deriving custom static response handlers.
|
||||
|
||||
The derived class can specify the RRsets to be included in the answer,
|
||||
authority, and additional sections of the response, whether to set the AA
|
||||
bit in the response, and a delay before sending the response.
|
||||
|
||||
The default implementation of `get_responses()` uses these properties to
|
||||
prepare and yield a single response.
|
||||
"""
|
||||
|
||||
@property
|
||||
def rcode(self) -> Optional[dns.rcode.Rcode]:
|
||||
"""
|
||||
Optional RCODE to be set in the response.
|
||||
"""
|
||||
return None
|
||||
|
||||
@property
|
||||
def answer(self) -> Sequence[dns.rrset.RRset]:
|
||||
"""
|
||||
RRsets to be included in the answer section of the response.
|
||||
"""
|
||||
return []
|
||||
|
||||
@property
|
||||
def authority(self) -> Sequence[dns.rrset.RRset]:
|
||||
"""
|
||||
RRsets to be included in the authority section of the response.
|
||||
"""
|
||||
return []
|
||||
|
||||
@property
|
||||
def additional(self) -> Sequence[dns.rrset.RRset]:
|
||||
"""
|
||||
RRsets to be included in the additional section of the response.
|
||||
"""
|
||||
return []
|
||||
|
||||
@property
|
||||
def authoritative(self) -> Optional[bool]:
|
||||
"""
|
||||
Whether to set the AA bit in the response.
|
||||
"""
|
||||
return None
|
||||
|
||||
@property
|
||||
def delay(self) -> float:
|
||||
"""
|
||||
Delay before sending the response.
|
||||
"""
|
||||
return 0.0
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
qctx.prepare_new_response(with_zone_data=False)
|
||||
qctx.response.answer.extend(self.answer)
|
||||
qctx.response.authority.extend(self.authority)
|
||||
qctx.response.additional.extend(self.additional)
|
||||
if self.rcode is not None:
|
||||
qctx.response.set_rcode(self.rcode)
|
||||
yield DnsResponseSend(
|
||||
qctx.response, authoritative=self.authoritative, delay=self.delay
|
||||
)
|
||||
|
||||
|
||||
class DomainHandler(ResponseHandler):
|
||||
"""
|
||||
Base class used for deriving custom domain handlers.
|
||||
|
|
@ -649,6 +748,8 @@ class DomainHandler(ResponseHandler):
|
|||
The derived class must specify a list of `domains` that it wants to handle.
|
||||
Queries for any of these domains (and their subdomains) will then be passed
|
||||
to the `get_response()` method in the derived class.
|
||||
|
||||
The most specific matching domain is stored in the `matched_domain` attribute.
|
||||
"""
|
||||
|
||||
@property
|
||||
|
|
@ -660,9 +761,15 @@ class DomainHandler(ResponseHandler):
|
|||
raise NotImplementedError
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._domains: List[dns.name.Name] = [
|
||||
dns.name.from_text(d) for d in self.domains
|
||||
]
|
||||
self._domains: List[dns.name.Name] = sorted(
|
||||
[dns.name.from_text(d) for d in self.domains], reverse=True
|
||||
)
|
||||
self._matched_domain: Optional[dns.name.Name] = None
|
||||
|
||||
@property
|
||||
def matched_domain(self) -> dns.name.Name:
|
||||
assert self._matched_domain is not None
|
||||
return self._matched_domain
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.__class__.__name__}(domains: {', '.join(self.domains)})"
|
||||
|
|
@ -672,8 +779,10 @@ class DomainHandler(ResponseHandler):
|
|||
Handle queries whose QNAME matches any of the domains handled by this
|
||||
class.
|
||||
"""
|
||||
self._matched_domain = None
|
||||
for domain in self._domains:
|
||||
if qctx.qname.is_subdomain(domain):
|
||||
self._matched_domain = domain
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
|
|||
|
|
@ -9,144 +9,60 @@
|
|||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
import socket
|
||||
import select
|
||||
from datetime import datetime, timedelta
|
||||
import time
|
||||
import functools
|
||||
from typing import AsyncGenerator
|
||||
|
||||
import dns, dns.message, dns.query, dns.flags
|
||||
from dns.rdatatype import *
|
||||
from dns.rdataclass import *
|
||||
from dns.rcode import *
|
||||
from dns.name import *
|
||||
import dns.rcode
|
||||
import dns.rdatatype
|
||||
|
||||
from isctest.asyncserver import (
|
||||
AsyncDnsServer,
|
||||
DnsResponseSend,
|
||||
QueryContext,
|
||||
ResponseHandler,
|
||||
)
|
||||
|
||||
from resolver_ans import rrset, soa_rrset
|
||||
|
||||
|
||||
# Log query to file
|
||||
def logquery(type, qname):
|
||||
with open("qlog", "a") as f:
|
||||
f.write("%s %s\n", type, qname)
|
||||
class EdnsWithOptionsFormerrHandler(ResponseHandler):
|
||||
def match(self, qctx: QueryContext) -> bool:
|
||||
return qctx.query.edns > -1 and qctx.query.options
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
qctx.response.set_rcode(dns.rcode.FORMERR)
|
||||
# The test requires that the server echoes back the client cookie
|
||||
qctx.response.opt = qctx.query.opt
|
||||
yield DnsResponseSend(qctx.response, authoritative=False)
|
||||
|
||||
|
||||
############################################################################
|
||||
# Respond to a DNS query.
|
||||
# If there are EDNS options present return FORMERR copying the OPT record.
|
||||
# Otherwise:
|
||||
# SOA gets a unsigned response.
|
||||
# NS gets a unsigned response.
|
||||
# A gets a unsigned response.
|
||||
# All other types get a unsigned NODATA response.
|
||||
############################################################################
|
||||
def create_response(msg):
|
||||
m = dns.message.from_wire(msg)
|
||||
qname = m.question[0].name.to_text()
|
||||
rrtype = m.question[0].rdtype
|
||||
typename = dns.rdatatype.to_text(rrtype)
|
||||
|
||||
with open("query.log", "a") as f:
|
||||
f.write("%s %s\n" % (typename, qname))
|
||||
print("%s %s" % (typename, qname), end=" ")
|
||||
|
||||
if m.edns != -1 and len(m.options) != 0:
|
||||
r = dns.message.make_response(m)
|
||||
r.use_edns(
|
||||
edns=m.edns, ednsflags=m.ednsflags, payload=m.payload, options=m.options
|
||||
)
|
||||
r.set_rcode(FORMERR)
|
||||
else:
|
||||
r = dns.message.make_response(m)
|
||||
r.set_rcode(NOERROR)
|
||||
if rrtype == A:
|
||||
r.answer.append(dns.rrset.from_text(qname, 1, IN, A, "10.53.0.10"))
|
||||
elif rrtype == NS:
|
||||
r.answer.append(dns.rrset.from_text(qname, 1, IN, NS, "."))
|
||||
elif rrtype == SOA:
|
||||
r.answer.append(dns.rrset.from_text(qname, 1, IN, SOA, ". . 0 0 0 0 0"))
|
||||
class FallbackHandler(ResponseHandler):
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
if qctx.qtype == dns.rdatatype.A:
|
||||
a_rrset = rrset(qctx.qname, dns.rdatatype.A, "10.53.0.10")
|
||||
qctx.response.answer.append(a_rrset)
|
||||
elif qctx.qtype == dns.rdatatype.NS:
|
||||
ns_rrset = rrset(qctx.qname, dns.rdatatype.NS, ".")
|
||||
qctx.response.answer.append(ns_rrset)
|
||||
elif qctx.qtype == dns.rdatatype.SOA:
|
||||
qctx.response.answer.append(soa_rrset(qctx.qname))
|
||||
else:
|
||||
r.authority.append(dns.rrset.from_text(qname, 1, IN, SOA, ". . 0 0 0 0 0"))
|
||||
r.flags |= dns.flags.AA
|
||||
return r
|
||||
qctx.response.authority.append(soa_rrset(qctx.qname))
|
||||
|
||||
yield DnsResponseSend(qctx.response, authoritative=True)
|
||||
|
||||
|
||||
def sigterm(signum, frame):
|
||||
print("Shutting down now...")
|
||||
os.remove("ans.pid")
|
||||
running = False
|
||||
sys.exit(0)
|
||||
def main() -> None:
|
||||
server = AsyncDnsServer(default_rcode=dns.rcode.NOERROR)
|
||||
server.install_response_handlers(
|
||||
EdnsWithOptionsFormerrHandler(),
|
||||
FallbackHandler(),
|
||||
)
|
||||
server.run()
|
||||
|
||||
|
||||
############################################################################
|
||||
# Main
|
||||
#
|
||||
# Set up responder and control channel, open the pid file, and start
|
||||
# the main loop, listening for queries on the query channel or commands
|
||||
# on the control channel and acting on them.
|
||||
############################################################################
|
||||
ip4 = "10.53.0.10"
|
||||
ip6 = "fd92:7065:b8e:ffff::10"
|
||||
|
||||
try:
|
||||
port = int(os.environ["PORT"])
|
||||
except:
|
||||
port = 5300
|
||||
|
||||
query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
query4_socket.bind((ip4, port))
|
||||
havev6 = True
|
||||
try:
|
||||
query6_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
try:
|
||||
query6_socket.bind((ip6, port))
|
||||
except:
|
||||
query6_socket.close()
|
||||
havev6 = False
|
||||
except:
|
||||
havev6 = False
|
||||
signal.signal(signal.SIGTERM, sigterm)
|
||||
|
||||
f = open("ans.pid", "w")
|
||||
pid = os.getpid()
|
||||
print(pid, file=f)
|
||||
f.close()
|
||||
|
||||
running = True
|
||||
|
||||
print("Listening on %s port %d" % (ip4, port))
|
||||
if havev6:
|
||||
print("Listening on %s port %d" % (ip6, port))
|
||||
print("Ctrl-c to quit")
|
||||
|
||||
if havev6:
|
||||
input = [query4_socket, query6_socket]
|
||||
else:
|
||||
input = [query4_socket]
|
||||
|
||||
while running:
|
||||
try:
|
||||
inputready, outputready, exceptready = select.select(input, [], [])
|
||||
except select.error as e:
|
||||
break
|
||||
except socket.error as e:
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
|
||||
for s in inputready:
|
||||
if s == query4_socket or s == query6_socket:
|
||||
print(
|
||||
"Query received on %s" % (ip4 if s == query4_socket else ip6), end=" "
|
||||
)
|
||||
# Handle incoming queries
|
||||
msg = s.recvfrom(65535)
|
||||
rsp = create_response(msg[0])
|
||||
if rsp:
|
||||
print(dns.rcode.to_text(rsp.rcode()))
|
||||
s.sendto(rsp.to_wire(), msg[1])
|
||||
else:
|
||||
print("NO RESPONSE")
|
||||
if not running:
|
||||
break
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -1,195 +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.
|
||||
|
||||
#
|
||||
# Ad hoc name server
|
||||
#
|
||||
|
||||
use IO::File;
|
||||
use IO::Socket;
|
||||
use Net::DNS;
|
||||
use Net::DNS::Packet;
|
||||
|
||||
print "Using Net::DNS $Net::DNS::VERSION\n";
|
||||
|
||||
my $localport = int($ENV{'PORT'});
|
||||
if (!$localport) { $localport = 5300; }
|
||||
|
||||
my $sock = IO::Socket::INET->new(LocalAddr => "10.53.0.2",
|
||||
LocalPort => $localport, Proto => "udp") or die "$!";
|
||||
|
||||
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;
|
||||
|
||||
for (;;) {
|
||||
$sock->recv($buf, 512);
|
||||
|
||||
print "**** request from " , $sock->peerhost, " port ", $sock->peerport, "\n";
|
||||
|
||||
my $packet;
|
||||
|
||||
if ($Net::DNS::VERSION > 0.68) {
|
||||
$packet = new Net::DNS::Packet(\$buf, 0);
|
||||
$@ and die $@;
|
||||
} else {
|
||||
my $err;
|
||||
($packet, $err) = new Net::DNS::Packet(\$buf, 0);
|
||||
$err and die $err;
|
||||
}
|
||||
|
||||
print "REQUEST:\n";
|
||||
$packet->print;
|
||||
|
||||
$packet->header->qr(1);
|
||||
|
||||
my @questions = $packet->question;
|
||||
my $qname = $questions[0]->qname;
|
||||
my $qtype = $questions[0]->qtype;
|
||||
|
||||
if ($qname eq "com" && $qtype eq "NS") {
|
||||
$packet->header->aa(1);
|
||||
$packet->push("answer", new Net::DNS::RR("com 300 NS a.root-servers.nil."));
|
||||
} elsif ($qname eq "example.com" && $qtype eq "NS") {
|
||||
$packet->header->aa(1);
|
||||
$packet->push("answer", new Net::DNS::RR("example.com 300 NS a.root-servers.nil."));
|
||||
} elsif ($qname eq "cname1.example.com") {
|
||||
# Data for the "cname + other data / 1" test
|
||||
$packet->push("answer", new Net::DNS::RR("cname1.example.com 300 CNAME cname1.example.com"));
|
||||
$packet->push("answer", new Net::DNS::RR("cname1.example.com 300 A 1.2.3.4"));
|
||||
} elsif ($qname eq "cname2.example.com") {
|
||||
# Data for the "cname + other data / 2" test: same RRs in opposite order
|
||||
$packet->push("answer", new Net::DNS::RR("cname2.example.com 300 A 1.2.3.4"));
|
||||
$packet->push("answer", new Net::DNS::RR("cname2.example.com 300 CNAME cname2.example.com"));
|
||||
} elsif ($qname =~ /redirect\.com/) {
|
||||
$packet->push("authority", new Net::DNS::RR("redirect.com 300 NS ns.redirect.com"));
|
||||
$packet->push("additional", new Net::DNS::RR("ns.redirect.com 300 A 10.53.0.6"));
|
||||
} elsif ($qname =~ /\.tld1/) {
|
||||
$packet->push("authority", new Net::DNS::RR("tld1 300 NS ns.tld1"));
|
||||
$packet->push("additional", new Net::DNS::RR("ns.tld1 300 A 10.53.0.6"));
|
||||
} elsif ($qname =~ /\.tld2/) {
|
||||
$packet->push("authority", new Net::DNS::RR("tld2 300 NS ns.tld2"));
|
||||
$packet->push("additional", new Net::DNS::RR("ns.tld2 300 A 10.53.0.7"));
|
||||
} elsif ($qname eq "org" && $qtype eq "NS") {
|
||||
$packet->header->aa(1);
|
||||
$packet->push("answer", new Net::DNS::RR("org 300 NS a.root-servers.nil."));
|
||||
} elsif ($qname eq "example.org" && $qtype eq "NS") {
|
||||
$packet->header->aa(1);
|
||||
$packet->push("answer", new Net::DNS::RR("example.org 300 NS a.root-servers.nil."));
|
||||
} elsif (($qname eq "baddname.example.org" || $qname eq "gooddname.example.org") && $qtype eq "NS") {
|
||||
$packet->header->aa(1);
|
||||
$packet->push("answer", new Net::DNS::RR("example.org 300 NS a.root-servers.nil."));
|
||||
} elsif ($qname eq "www.example.org" ||
|
||||
$qname eq "badcname.example.org" ||
|
||||
$qname eq "goodcname.example.org" ||
|
||||
$qname eq "foo.baddname.example.org" ||
|
||||
$qname eq "foo.gooddname.example.org") {
|
||||
# Data for address/alias filtering.
|
||||
$packet->header->aa(1);
|
||||
if ($qtype eq "A") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname .
|
||||
" 300 A 192.0.2.1"));
|
||||
} elsif ($qtype eq "AAAA") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname .
|
||||
" 300 AAAA 2001:db8:beef::1"));
|
||||
}
|
||||
} elsif ($qname eq "net" && $qtype eq "NS") {
|
||||
$packet->header->aa(1);
|
||||
$packet->push("answer", new Net::DNS::RR("net 300 NS a.root-servers.nil."));
|
||||
} elsif ($qname eq "noresponse.exampleudp.net") {
|
||||
next;
|
||||
} elsif ($qname =~ /example\.net/) {
|
||||
$packet->push("authority", new Net::DNS::RR("example.net 300 NS ns.example.net"));
|
||||
$packet->push("additional", new Net::DNS::RR("ns.example.net 300 A 10.53.0.3"));
|
||||
} elsif ($qname =~ /exampleudp\.net/) {
|
||||
$packet->push("authority", new Net::DNS::RR("exampleudp.net 300 NS ns.exampleudp.net"));
|
||||
$packet->push("additional", new Net::DNS::RR("ns.exampleudp.net 300 A 10.53.0.2"));
|
||||
} elsif ($qname =~ /lame\.example\.org/) {
|
||||
$packet->header->ad(0);
|
||||
$packet->header->aa(0);
|
||||
$packet->push("authority", new Net::DNS::RR("lame.example.org 300 NS ns.lame.example.org"));
|
||||
$packet->push("additional", new Net::DNS::RR("ns.lame.example.org 300 A 10.53.0.3"));
|
||||
} elsif ($qname =~ /sub\.example\.org/) {
|
||||
# Data for CNAME/DNAME filtering. The final answers are
|
||||
# expected to be accepted regardless of the filter setting.
|
||||
$packet->push("authority", new Net::DNS::RR("sub.example.org 300 NS ns.sub.example.org"));
|
||||
$packet->push("additional", new Net::DNS::RR("ns.sub.example.org 300 A 10.53.0.3"));
|
||||
} elsif ($qname =~ /glue-in-answer\.example\.org/) {
|
||||
$packet->push("answer", new Net::DNS::RR("ns.glue-in-answer.example.org 300 A 10.53.0.3"));
|
||||
$packet->push("authority", new Net::DNS::RR("glue-in-answer.example.org 300 NS ns.glue-in-answer.example.org"));
|
||||
$packet->push("additional", new Net::DNS::RR("ns.glue-in-answer.example.org 300 A 10.53.0.3"));
|
||||
} elsif ($qname =~ /\.broken/ || $qname =~ /^broken/) {
|
||||
# Delegation to broken TLD.
|
||||
$packet->push("authority", new Net::DNS::RR("broken 300 NS ns.broken"));
|
||||
$packet->push("additional", new Net::DNS::RR("ns.broken 300 A 10.53.0.4"));
|
||||
} elsif ($qname =~ /\.partial-formerr/) {
|
||||
$packet->header->rcode("FORMERR");
|
||||
} elsif ($qname eq "gl6412") {
|
||||
if ($qtype eq "SOA") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0"));
|
||||
} elsif ($qtype eq "NS") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname . " 300 NS ns2" . $qname));
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname . " 300 NS ns3" . $qname));
|
||||
} else {
|
||||
$packet->push("authority",
|
||||
new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0"));
|
||||
}
|
||||
} elsif ($qname eq "a.gl6412" || $qname eq "a.a.gl6412") {
|
||||
$packet->push("authority",
|
||||
new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0"));
|
||||
} elsif ($qname eq "ns2.gl6412") {
|
||||
if ($qtype eq "A") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname . " 300 A 10.53.0.2"));
|
||||
} else {
|
||||
$packet->push("authority",
|
||||
new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0"));
|
||||
}
|
||||
} elsif ($qname eq "ns3.gl6412") {
|
||||
if ($qtype eq "A") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname . " 300 A 10.53.0.3"));
|
||||
} else {
|
||||
$packet->push("authority",
|
||||
new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0"));
|
||||
}
|
||||
} elsif ($qname eq "zoneversion") {
|
||||
$packet->push("authority", new Net::DNS::RR(". 300 SOA . . 0 0 0 0 0"));
|
||||
if ($Net::DNS::VERSION >= 1.49) {
|
||||
$packet->edns->option('ZONEVERSION' => [0, 1, '01022304'] )
|
||||
} elsif ($Net::DNS::VERSION >= 1.35) {
|
||||
$packet->edns->option('19' => {'BASE16' => '000101022304'} )
|
||||
} else {
|
||||
$packet->edns->option('19' => pack 'H*', '000101022304')
|
||||
}
|
||||
} else {
|
||||
# Data for the "bogus referrals" test
|
||||
$packet->push("authority", new Net::DNS::RR("below.www.example.com 300 NS ns.below.www.example.com"));
|
||||
$packet->push("additional", new Net::DNS::RR("ns.below.www.example.com 300 A 10.53.0.3"));
|
||||
}
|
||||
|
||||
$sock->send($packet->data);
|
||||
|
||||
print "RESPONSE:\n";
|
||||
$packet->print;
|
||||
print "\n";
|
||||
}
|
||||
229
bin/tests/system/resolver/ans2/ans.py
Normal file
229
bin/tests/system/resolver/ans2/ans.py
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
"""
|
||||
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 typing import AsyncGenerator, Tuple, Union
|
||||
|
||||
import dns.edns
|
||||
import dns.name
|
||||
import dns.rcode
|
||||
import dns.rrset
|
||||
import dns.rdatatype
|
||||
|
||||
from isctest.asyncserver import (
|
||||
AsyncDnsServer,
|
||||
DnsResponseSend,
|
||||
DomainHandler,
|
||||
IgnoreAllQueries,
|
||||
QnameHandler,
|
||||
QnameQtypeHandler,
|
||||
QueryContext,
|
||||
StaticResponseHandler,
|
||||
ResponseHandler,
|
||||
)
|
||||
|
||||
from resolver_ans import (
|
||||
DelegationHandler,
|
||||
Gl6412AHandler,
|
||||
Gl6412Handler,
|
||||
Gl6412Ns2Handler,
|
||||
Gl6412Ns3Handler,
|
||||
rrset,
|
||||
setup_delegation,
|
||||
soa_rrset,
|
||||
)
|
||||
|
||||
|
||||
class BadGoodDnameNsHandler(QnameQtypeHandler, StaticResponseHandler):
|
||||
qnames = [
|
||||
"baddname.example.org.",
|
||||
"gooddname.example.org.",
|
||||
]
|
||||
qtypes = [dns.rdatatype.NS]
|
||||
answer = [rrset("example.org.", dns.rdatatype.NS, "a.root-servers.nil.")]
|
||||
authoritative = True
|
||||
|
||||
|
||||
def _cname_rrsets(
|
||||
qname: Union[dns.name.Name, str],
|
||||
) -> Tuple[dns.rrset.RRset, dns.rrset.RRset]:
|
||||
return (
|
||||
rrset(qname, dns.rdatatype.CNAME, f"{qname}"),
|
||||
rrset(qname, dns.rdatatype.A, "1.2.3.4"),
|
||||
)
|
||||
|
||||
|
||||
class Cname1Handler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["cname1.example.com."]
|
||||
# Data for the "cname + other data / 1" test
|
||||
answer = _cname_rrsets(qnames[0])
|
||||
authoritative = False
|
||||
|
||||
|
||||
class Cname2Handler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["cname2.example.com."]
|
||||
# Data for the "cname + other data / 2" test: same RRs in opposite order
|
||||
answer = tuple(reversed(_cname_rrsets(qnames[0])))
|
||||
authoritative = False
|
||||
|
||||
|
||||
class ExampleOrgHandler(QnameHandler):
|
||||
qnames = [
|
||||
"www.example.org",
|
||||
"badcname.example.org",
|
||||
"goodcname.example.org",
|
||||
"foo.baddname.example.org",
|
||||
"foo.gooddname.example.org",
|
||||
]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
# Data for address/alias filtering.
|
||||
if qctx.qtype == dns.rdatatype.A:
|
||||
a_rrset = rrset(qctx.qname, dns.rdatatype.A, "192.0.2.1")
|
||||
qctx.response.answer.append(a_rrset)
|
||||
elif qctx.qtype == dns.rdatatype.AAAA:
|
||||
aaaa_rrset = rrset(qctx.qname, dns.rdatatype.AAAA, "2001:db8:beef::1")
|
||||
qctx.response.answer.append(aaaa_rrset)
|
||||
yield DnsResponseSend(qctx.response, authoritative=True)
|
||||
|
||||
|
||||
class NoResponseExampleUdpHandler(QnameHandler, IgnoreAllQueries):
|
||||
qnames = ["noresponse.exampleudp.net."]
|
||||
|
||||
|
||||
class RootNsHandler(QnameQtypeHandler):
|
||||
qnames = [
|
||||
"example.com.",
|
||||
"com.",
|
||||
"example.org.",
|
||||
"org.",
|
||||
"net.",
|
||||
]
|
||||
|
||||
qtypes = [dns.rdatatype.NS]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
root_ns = rrset(qctx.qname, dns.rdatatype.NS, "a.root-servers.nil.")
|
||||
qctx.response.answer.append(root_ns)
|
||||
yield DnsResponseSend(qctx.response, authoritative=True)
|
||||
|
||||
|
||||
class ZoneVersionHandler(QnameHandler):
|
||||
qnames = ["zoneversion."]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
qctx.response.authority.append(soa_rrset("."))
|
||||
zoneversion_opt = dns.edns.GenericOption(19, bytes.fromhex("000101022304")) # type: ignore
|
||||
qctx.response.use_edns(edns=0, options=[zoneversion_opt])
|
||||
yield DnsResponseSend(qctx.response, authoritative=False)
|
||||
|
||||
|
||||
class Ns2Delegation(DelegationHandler):
|
||||
domains = ["exampleudp.net."]
|
||||
server_number = 2
|
||||
|
||||
|
||||
class Ns3Delegation(DelegationHandler):
|
||||
domains = [
|
||||
"example.net.",
|
||||
"lame.example.org.",
|
||||
"sub.example.org.",
|
||||
]
|
||||
server_number = 3
|
||||
|
||||
|
||||
class Ns3GlueInAnswerDelegation(DelegationHandler):
|
||||
domains = ["glue-in-answer.example.org."]
|
||||
server_number = 3
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
async for dns_response in super().get_responses(qctx):
|
||||
dns_response.response.answer += dns_response.response.additional
|
||||
yield dns_response
|
||||
|
||||
|
||||
class Ns4Delegation(DelegationHandler):
|
||||
domains = ["broken."]
|
||||
server_number = 4
|
||||
|
||||
|
||||
class Ns6Delegation(DelegationHandler):
|
||||
domains = [
|
||||
"redirect.com.",
|
||||
"tld1.",
|
||||
]
|
||||
server_number = 6
|
||||
|
||||
|
||||
class Ns7Delegation(DelegationHandler):
|
||||
domains = ["tld2."]
|
||||
server_number = 7
|
||||
|
||||
|
||||
class PartialFormerrHandler(DomainHandler, StaticResponseHandler):
|
||||
domains = ["partial-formerr."]
|
||||
authoritative = False
|
||||
rcode = dns.rcode.FORMERR
|
||||
|
||||
|
||||
class FallbackHandler(ResponseHandler):
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
setup_delegation(qctx, "below.www.example.com.", 3)
|
||||
yield DnsResponseSend(qctx.response, authoritative=False)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
server = AsyncDnsServer(default_rcode=dns.rcode.NOERROR)
|
||||
|
||||
# Install QnameHandlers first
|
||||
server.install_response_handlers(
|
||||
BadGoodDnameNsHandler(),
|
||||
Cname1Handler(),
|
||||
Cname2Handler(),
|
||||
ExampleOrgHandler(),
|
||||
Gl6412AHandler(),
|
||||
Gl6412Handler(),
|
||||
Gl6412Ns2Handler(),
|
||||
Gl6412Ns3Handler(),
|
||||
NoResponseExampleUdpHandler(),
|
||||
RootNsHandler(),
|
||||
ZoneVersionHandler(),
|
||||
)
|
||||
|
||||
# Then install DomainHandlers
|
||||
server.install_response_handlers(
|
||||
Ns2Delegation(),
|
||||
Ns3Delegation(),
|
||||
Ns3GlueInAnswerDelegation(),
|
||||
Ns4Delegation(),
|
||||
Ns6Delegation(),
|
||||
Ns7Delegation(),
|
||||
PartialFormerrHandler(),
|
||||
)
|
||||
|
||||
# Finally, install the fallback handler
|
||||
server.install_response_handler(FallbackHandler())
|
||||
server.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,234 +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.
|
||||
|
||||
#
|
||||
# Ad hoc name server
|
||||
#
|
||||
|
||||
use IO::File;
|
||||
use IO::Socket;
|
||||
use Net::DNS;
|
||||
use Net::DNS::Packet;
|
||||
|
||||
# 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;
|
||||
|
||||
my $localport = int($ENV{'PORT'});
|
||||
if (!$localport) { $localport = 5300; }
|
||||
|
||||
my $server_addr = "10.53.0.3";
|
||||
|
||||
my $udpsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
|
||||
LocalPort => $localport, Proto => "udp", Reuse => 1) or die "$!";
|
||||
my $tcpsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
|
||||
LocalPort => $localport, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!";
|
||||
|
||||
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;
|
||||
|
||||
sub handleQuery {
|
||||
my $buf = shift;
|
||||
my $packet;
|
||||
|
||||
if ($Net::DNS::VERSION > 0.68) {
|
||||
$packet = new Net::DNS::Packet(\$buf, 0);
|
||||
$@ and die $@;
|
||||
} else {
|
||||
my $err;
|
||||
($packet, $err) = new Net::DNS::Packet(\$buf, 0);
|
||||
$err and die $err;
|
||||
}
|
||||
|
||||
print "REQUEST:\n";
|
||||
$packet->print;
|
||||
|
||||
$packet->header->qr(1);
|
||||
$packet->header->aa(1);
|
||||
|
||||
my @questions = $packet->question;
|
||||
my $qname = $questions[0]->qname;
|
||||
my $qtype = $questions[0]->qtype;
|
||||
|
||||
if ($qname eq "example.net" && $qtype eq "NS") {
|
||||
$packet->push("answer", new Net::DNS::RR($qname . " 300 NS ns.example.net"));
|
||||
$packet->push("additional", new Net::DNS::RR("ns.example.net 300 A 10.53.0.3"));
|
||||
} elsif ($qname eq "ns.example.net") {
|
||||
$packet->push("answer", new Net::DNS::RR($qname . " 300 A 10.53.0.3"));
|
||||
} elsif ($qname eq "nodata.example.net") {
|
||||
# Do not add a SOA RRset.
|
||||
} elsif ($qname eq "noresponse.example.net") {
|
||||
# Do not response.
|
||||
print "RESPONSE:\n";
|
||||
return "";
|
||||
} elsif ($qname eq "nxdomain.example.net") {
|
||||
# Do not add a SOA RRset.
|
||||
$packet->header->rcode(NXDOMAIN);
|
||||
} elsif ($qname eq "www.example.net") {
|
||||
# Data for address/alias filtering.
|
||||
if ($qtype eq "A") {
|
||||
$packet->push("answer", new Net::DNS::RR($qname . " 300 A 192.0.2.1"));
|
||||
} elsif ($qtype eq "AAAA") {
|
||||
$packet->push("answer", new Net::DNS::RR($qname . " 300 AAAA 2001:db8:beef::1"));
|
||||
}
|
||||
} elsif ($qname eq "badcname.example.net") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname .
|
||||
" 300 CNAME badcname.example.org"));
|
||||
} elsif (($qname eq "baddname.example.net" || $qname eq "gooddname.example.net") && $qtype eq "NS") {
|
||||
$packet->push("authority", new Net::DNS::RR("example.net IN SOA (1 2 3 4 5)"))
|
||||
} elsif ($qname eq "foo.baddname.example.net") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR("baddname.example.net" .
|
||||
" 300 DNAME baddname.example.org"));
|
||||
} elsif ($qname eq "foo.gooddname.example.net") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR("gooddname.example.net" .
|
||||
" 300 DNAME gooddname.example.org"));
|
||||
} elsif ($qname eq "goodcname.example.net") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname .
|
||||
" 300 CNAME goodcname.example.org"));
|
||||
} elsif ($qname =~ /^longcname/) {
|
||||
$cname = $qname =~ s/longcname/longcnamex/r;
|
||||
$packet->push("answer", new Net::DNS::RR($qname . " 300 CNAME " . $cname));
|
||||
} elsif ($qname =~ /^nodata\.example\.net$/i) {
|
||||
$packet->header->aa(1);
|
||||
} elsif ($qname =~ /^nxdomain\.example\.net$/i) {
|
||||
$packet->header->aa(1);
|
||||
$packet->header->rcode(NXDOMAIN);
|
||||
} elsif ($qname =~ /lame\.example\.org/) {
|
||||
$packet->header->ad(0);
|
||||
$packet->header->aa(0);
|
||||
$packet->push("authority", new Net::DNS::RR("lame.example.org 300 NS ns.lame.example.org"));
|
||||
$packet->push("additional", new Net::DNS::RR("ns.lame.example.org 300 A 10.53.0.3"));
|
||||
} elsif ($qname eq "large-referral.example.net") {
|
||||
for (my $i = 1; $i < 1000; $i++) {
|
||||
$packet->push("authority", new Net::DNS::RR("large-referral.example.net 300 NS ns" . $i . ".fake.redirect.com"));
|
||||
}
|
||||
# No glue records
|
||||
} elsif ($qname eq "foo.bar.sub.tld1") {
|
||||
$packet->push("answer", new Net::DNS::RR("$qname 300 TXT baz"));
|
||||
} elsif ($qname eq "cname.sub.example.org") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname .
|
||||
" 300 CNAME ok.sub.example.org"));
|
||||
} elsif ($qname eq "ok.sub.example.org") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname . " 300 A 192.0.2.1"));
|
||||
} elsif ($qname eq "www.dname.sub.example.org") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR("dname.sub.example.org" .
|
||||
" 300 DNAME ok.sub.example.org"));
|
||||
} elsif ($qname eq "www.ok.sub.example.org") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname . " 300 A 192.0.2.1"));
|
||||
} elsif ($qname eq "foo.glue-in-answer.example.org") {
|
||||
$packet->push("answer", new Net::DNS::RR($qname . " 300 A 192.0.2.1"));
|
||||
} elsif ($qname eq "ns.example.net") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname .
|
||||
" 300 A 10.53.0.3"));
|
||||
} elsif ($qname =~ /\.partial-formerr/) {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname . " 1 A 10.53.0.3"));
|
||||
} elsif ($qname eq "gl6412") {
|
||||
if ($qtype eq "SOA") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0"));
|
||||
} elsif ($qtype eq "NS") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname . " 300 NS ns2" . $qname));
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname . " 300 NS ns3" . $qname));
|
||||
} else {
|
||||
$packet->push("authority",
|
||||
new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0"));
|
||||
}
|
||||
} elsif ($qname eq "a.gl6412" || $qname eq "a.a.gl6412") {
|
||||
$packet->push("authority",
|
||||
new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0"));
|
||||
} elsif ($qname eq "ns2.gl6412") {
|
||||
if ($qtype eq "A") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname . " 300 A 10.53.0.2"));
|
||||
} else {
|
||||
$packet->push("authority",
|
||||
new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0"));
|
||||
}
|
||||
} elsif ($qname eq "ns3.gl6412") {
|
||||
if ($qtype eq "A") {
|
||||
$packet->push("answer",
|
||||
new Net::DNS::RR($qname . " 300 A 10.53.0.3"));
|
||||
} else {
|
||||
$packet->push("authority",
|
||||
new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0"));
|
||||
}
|
||||
} else {
|
||||
$packet->push("answer", new Net::DNS::RR("www.example.com 300 A 1.2.3.4"));
|
||||
}
|
||||
|
||||
print "RESPONSE:\n";
|
||||
$packet->print;
|
||||
|
||||
return $packet->data;
|
||||
}
|
||||
|
||||
# Main
|
||||
my $rin;
|
||||
my $rout;
|
||||
for (;;) {
|
||||
$rin = '';
|
||||
vec($rin, fileno($tcpsock), 1) = 1;
|
||||
vec($rin, fileno($udpsock), 1) = 1;
|
||||
|
||||
select($rout = $rin, undef, undef, undef);
|
||||
|
||||
if (vec($rout, fileno($udpsock), 1)) {
|
||||
printf "UDP request\n";
|
||||
my $buf;
|
||||
$udpsock->recv($buf, 512);
|
||||
my $result = handleQuery($buf);
|
||||
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 = handleQuery($buf);
|
||||
$len = length($result);
|
||||
if ($len != 0) {
|
||||
$conn->syswrite(pack("n", $len), 2);
|
||||
$n = $conn->syswrite($result, $len);
|
||||
} else {
|
||||
$n = 0;
|
||||
}
|
||||
print " Sent: $n chars via TCP\n";
|
||||
}
|
||||
$conn->close;
|
||||
}
|
||||
}
|
||||
228
bin/tests/system/resolver/ans3/ans.py
Normal file
228
bin/tests/system/resolver/ans3/ans.py
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
"""
|
||||
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 typing import AsyncGenerator
|
||||
|
||||
import dns.name
|
||||
import dns.rcode
|
||||
import dns.rdatatype
|
||||
|
||||
from isctest.asyncserver import (
|
||||
AsyncDnsServer,
|
||||
DnsResponseSend,
|
||||
DomainHandler,
|
||||
IgnoreAllQueries,
|
||||
QnameHandler,
|
||||
QnameQtypeHandler,
|
||||
QueryContext,
|
||||
StaticResponseHandler,
|
||||
ResponseHandler,
|
||||
)
|
||||
|
||||
from resolver_ans import (
|
||||
DelegationHandler,
|
||||
Gl6412AHandler,
|
||||
Gl6412Handler,
|
||||
Gl6412Ns2Handler,
|
||||
Gl6412Ns3Handler,
|
||||
rrset,
|
||||
rrset_from_list,
|
||||
soa_rrset,
|
||||
)
|
||||
|
||||
|
||||
class ApexNSHandler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["example.net."]
|
||||
qtypes = [dns.rdatatype.NS]
|
||||
answer = [rrset(qnames[0], dns.rdatatype.NS, f"ns.{qnames[0]}")]
|
||||
additional = [rrset(f"ns.{qnames[0]}", dns.rdatatype.A, "10.53.0.3")]
|
||||
|
||||
|
||||
class BadCnameHandler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["badcname.example.net."]
|
||||
answer = [rrset(qnames[0], dns.rdatatype.CNAME, "badcname.example.org.")]
|
||||
|
||||
|
||||
class BadGoodDnameNsHandler(QnameQtypeHandler, StaticResponseHandler):
|
||||
qnames = ["baddname.example.net.", "gooddname.example.net."]
|
||||
qtypes = [dns.rdatatype.NS]
|
||||
authority = [soa_rrset("example.net.")]
|
||||
|
||||
|
||||
class CnameSubHandler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["cname.sub.example.org."]
|
||||
answer = [rrset(qnames[0], dns.rdatatype.CNAME, "ok.sub.example.org.")]
|
||||
|
||||
|
||||
class FooBadDnameHandler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["foo.baddname.example.net."]
|
||||
answer = [
|
||||
rrset("baddname.example.net.", dns.rdatatype.DNAME, "baddname.example.org.")
|
||||
]
|
||||
|
||||
|
||||
class FooBarSubTld1Handler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["foo.bar.sub.tld1."]
|
||||
answer = [rrset(qnames[0], dns.rdatatype.TXT, "baz")]
|
||||
|
||||
|
||||
class FooGlueInAnswerHandler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["foo.glue-in-answer.example.org."]
|
||||
answer = [rrset(qnames[0], dns.rdatatype.A, "192.0.2.1")]
|
||||
|
||||
|
||||
class FooGoodDnameHandler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["foo.gooddname.example.net."]
|
||||
answer = [
|
||||
rrset("gooddname.example.net.", dns.rdatatype.DNAME, "gooddname.example.org.")
|
||||
]
|
||||
|
||||
|
||||
class GoodCnameHandler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["goodcname.example.net."]
|
||||
answer = [rrset(qnames[0], dns.rdatatype.CNAME, "goodcname.example.org.")]
|
||||
|
||||
|
||||
class LameExampleOrgDelegation(DelegationHandler):
|
||||
domains = ["lame.example.org."]
|
||||
server_number = 3
|
||||
|
||||
|
||||
class LargeReferralHandler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["large-referral.example.net."]
|
||||
qtypes = [dns.rdatatype.NS]
|
||||
authority = [
|
||||
rrset_from_list(
|
||||
qnames[0],
|
||||
dns.rdatatype.NS,
|
||||
[f"ns{i}.fake.redirect.com." for i in range(1, 1000)],
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class LongCnameHandler(ResponseHandler):
|
||||
def match(self, qctx: QueryContext) -> bool:
|
||||
return qctx.qname.labels[0].startswith(b"longcname")
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
first_label = qctx.qname.labels[0].replace(b"longcname", b"longcnamex")
|
||||
cname_target = f"{dns.name.Name((first_label,) + qctx.qname.labels[1:])}"
|
||||
qctx.response.answer.append(
|
||||
rrset(qctx.qname, dns.rdatatype.CNAME, cname_target)
|
||||
)
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
class NodataHandler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["nodata.example.net."]
|
||||
|
||||
|
||||
class NoresponseHandler(QnameHandler, IgnoreAllQueries):
|
||||
qnames = ["noresponse.example.net."]
|
||||
|
||||
|
||||
class NsHandler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["ns.example.net."]
|
||||
answer = [rrset(qnames[0], dns.rdatatype.A, "10.53.0.3")]
|
||||
|
||||
|
||||
class NxdomainHandler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["nxdomain.example.net."]
|
||||
rcode = dns.rcode.NXDOMAIN
|
||||
|
||||
|
||||
class OkSubHandler(QnameHandler):
|
||||
qnames = ["ok.sub.example.org.", "www.ok.sub.example.org."]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
qctx.response.answer.append(rrset(qctx.qname, dns.rdatatype.A, "192.0.2.1"))
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
class PartialFormerrHandler(DomainHandler):
|
||||
domains = ["partial-formerr."]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
qctx.response.answer.append(
|
||||
rrset(qctx.qname, dns.rdatatype.A, "10.53.0.3", ttl=1)
|
||||
)
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
class WwwDnameSubHandler(QnameHandler, StaticResponseHandler):
|
||||
qnames = ["www.dname.sub.example.org."]
|
||||
answer = [
|
||||
rrset("dname.sub.example.org.", dns.rdatatype.DNAME, "ok.sub.example.org.")
|
||||
]
|
||||
|
||||
|
||||
class WwwHandler(QnameHandler):
|
||||
qnames = ["www.example.net."]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
if qctx.qtype == dns.rdatatype.A:
|
||||
qctx.response.answer.append(rrset(qctx.qname, dns.rdatatype.A, "192.0.2.1"))
|
||||
elif qctx.qtype == dns.rdatatype.AAAA:
|
||||
qctx.response.answer.append(
|
||||
rrset(qctx.qname, dns.rdatatype.AAAA, "2001:db8:beef::1")
|
||||
)
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
class FallbackHandler(StaticResponseHandler):
|
||||
answer = [rrset("www.example.com.", dns.rdatatype.A, "1.2.3.4")]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
|
||||
server.install_response_handlers(
|
||||
ApexNSHandler(),
|
||||
BadCnameHandler(),
|
||||
BadGoodDnameNsHandler(),
|
||||
CnameSubHandler(),
|
||||
FooBadDnameHandler(),
|
||||
FooBarSubTld1Handler(),
|
||||
FooGoodDnameHandler(),
|
||||
FooGlueInAnswerHandler(),
|
||||
Gl6412AHandler(),
|
||||
Gl6412Handler(),
|
||||
Gl6412Ns2Handler(),
|
||||
Gl6412Ns3Handler(),
|
||||
GoodCnameHandler(),
|
||||
LameExampleOrgDelegation(),
|
||||
LargeReferralHandler(),
|
||||
LongCnameHandler(),
|
||||
NodataHandler(),
|
||||
NoresponseHandler(),
|
||||
NsHandler(),
|
||||
NxdomainHandler(),
|
||||
OkSubHandler(),
|
||||
PartialFormerrHandler(),
|
||||
WwwDnameSubHandler(),
|
||||
WwwHandler(),
|
||||
)
|
||||
|
||||
server.install_response_handler(FallbackHandler())
|
||||
server.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,177 +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.
|
||||
|
||||
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;
|
||||
|
||||
my $server_addr = "10.53.0.8";
|
||||
|
||||
my $localport = int($ENV{'PORT'});
|
||||
if (!$localport) { $localport = 5300; }
|
||||
|
||||
my $udpsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
|
||||
LocalPort => $localport, Proto => "udp", Reuse => 1) or die "$!";
|
||||
my $tcpsock = IO::Socket::INET->new(LocalAddr => "$server_addr",
|
||||
LocalPort => $localport, Proto => "tcp", Listen => 5, Reuse => 1) or die "$!";
|
||||
|
||||
print "listening on $server_addr:$localport.\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;
|
||||
|
||||
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 $response = new Net::DNS::Packet($qname, $qtype, $qclass);
|
||||
$response->header->qr(1);
|
||||
$response->header->aa(1);
|
||||
$response->header->tc(0);
|
||||
$response->header->id($id);
|
||||
|
||||
# Responses to queries for no-questions/NS and ns.no-questions/A are
|
||||
# _not_ malformed or truncated.
|
||||
if ($qname eq "no-questions" && $qtype eq "NS") {
|
||||
$response->push("answer", new Net::DNS::RR($qname . " 300 NS ns.no-questions"));
|
||||
$response->push("additional", new Net::DNS::RR("ns.no-questions. 300 A 10.53.0.8"));
|
||||
return $response->data;
|
||||
} elsif ($qname eq "ns.no-questions") {
|
||||
$response->push("answer", new Net::DNS::RR($qname . " 300 A 10.53.0.8"))
|
||||
if ($qtype eq "A");
|
||||
return $response->data;
|
||||
} elsif ($qname =~ /\.formerr-to-all$/) {
|
||||
$response->header->rcode("FORMERR");
|
||||
return $response->data;
|
||||
}
|
||||
|
||||
# don't use Net::DNS to construct the header only reply as early
|
||||
# versions just get it completely wrong.
|
||||
|
||||
if ($qname eq "truncated.no-questions") {
|
||||
# QR, AA, TC: forces TCP retry
|
||||
return (pack("nnnnnn", $id, 0x8600, 0, 0, 0, 0));
|
||||
} elsif ($qname eq "tcpalso.no-questions") {
|
||||
# QR, REFUSED: forces TCP retry
|
||||
return (pack("nnnnnn", $id, 0x8205, 0, 0, 0, 0));
|
||||
}
|
||||
# QR, AA
|
||||
return (pack("nnnnnn", $id, 0x8400, 0, 0, 0, 0));
|
||||
}
|
||||
|
||||
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 @results = ();
|
||||
my $response = new Net::DNS::Packet($qname, $qtype, $qclass);
|
||||
|
||||
$response->header->qr(1);
|
||||
$response->header->aa(1);
|
||||
$response->header->id($id);
|
||||
$response->push("answer", new Net::DNS::RR("$qname 300 A 1.2.3.4"));
|
||||
|
||||
if ($qname eq "tcpalso.no-questions") {
|
||||
# for this qname we also return a bad reply over TCP
|
||||
# QR, REFUSED, no question section
|
||||
push (@results, pack("nnnnnn", $id, 0x8005, 0, 0, 0, 0));
|
||||
} else {
|
||||
push(@results, $response->data);
|
||||
}
|
||||
|
||||
return \@results;
|
||||
}
|
||||
|
||||
# Main
|
||||
my $rin;
|
||||
my $rout;
|
||||
for (;;) {
|
||||
$rin = '';
|
||||
vec($rin, fileno($tcpsock), 1) = 1;
|
||||
vec($rin, fileno($udpsock), 1) = 1;
|
||||
|
||||
select($rout = $rin, undef, undef, undef);
|
||||
|
||||
if (vec($rout, fileno($udpsock), 1)) {
|
||||
printf "UDP request\n";
|
||||
my $buf;
|
||||
$udpsock->recv($buf, 512);
|
||||
my $result = handleUDP($buf);
|
||||
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 = handleTCP($buf);
|
||||
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;
|
||||
}
|
||||
}
|
||||
144
bin/tests/system/resolver/ans8/ans.py
Normal file
144
bin/tests/system/resolver/ans8/ans.py
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
import abc
|
||||
|
||||
from typing import AsyncGenerator
|
||||
|
||||
import dns.flags
|
||||
import dns.message
|
||||
import dns.rcode
|
||||
import dns.rdatatype
|
||||
|
||||
from isctest.asyncserver import (
|
||||
AsyncDnsServer,
|
||||
DnsResponseSend,
|
||||
DnsProtocol,
|
||||
DomainHandler,
|
||||
QnameHandler,
|
||||
QnameQtypeHandler,
|
||||
QueryContext,
|
||||
ResponseHandler,
|
||||
StaticResponseHandler,
|
||||
)
|
||||
|
||||
from resolver_ans import rrset
|
||||
|
||||
|
||||
class HeaderOnlyHandler(ResponseHandler):
|
||||
"""
|
||||
Return an empty DNS message with only header flags set.
|
||||
"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def flags(self) -> dns.flags.Flag:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def rcode(self) -> dns.rcode.Rcode:
|
||||
return dns.rcode.NOERROR
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
message = dns.message.Message(id=qctx.query.id)
|
||||
message.use_edns(False)
|
||||
message.flags = self.flags
|
||||
message.set_rcode(self.rcode)
|
||||
yield DnsResponseSend(message, acknowledge_hand_rolled_response=True)
|
||||
|
||||
|
||||
class RefusedOnTcpHandler(QnameHandler, HeaderOnlyHandler):
|
||||
qnames = ["tcpalso.no-questions."]
|
||||
flags = dns.flags.QR
|
||||
rcode = dns.rcode.REFUSED
|
||||
|
||||
def match(self, qctx: QueryContext) -> bool:
|
||||
return qctx.protocol == DnsProtocol.TCP and super().match(qctx)
|
||||
|
||||
|
||||
class TcpFallbackHandler(ResponseHandler):
|
||||
def match(self, qctx: QueryContext) -> bool:
|
||||
return qctx.protocol == DnsProtocol.TCP
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
qctx.response.answer.append(rrset(qctx.qname, dns.rdatatype.A, "1.2.3.4"))
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
class FormerrToAllHandler(DomainHandler, StaticResponseHandler):
|
||||
domains = ["formerr-to-all."]
|
||||
rcode = dns.rcode.FORMERR
|
||||
|
||||
|
||||
class NoQuestionsNSHandler(QnameQtypeHandler, StaticResponseHandler):
|
||||
qnames = ["no-questions."]
|
||||
qtypes = [dns.rdatatype.NS]
|
||||
answer = [rrset(qnames[0], dns.rdatatype.NS, f"ns.{qnames[0]}")]
|
||||
additional = [rrset(f"ns.{qnames[0]}", dns.rdatatype.A, "10.53.0.8")]
|
||||
|
||||
|
||||
class NsNoQuestionsAHandler(QnameHandler):
|
||||
qnames = ["ns.no-questions."]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
if qctx.qtype == dns.rdatatype.A:
|
||||
a_rrset = rrset(qctx.qname, dns.rdatatype.A, "10.53.0.8")
|
||||
qctx.response.answer.append(a_rrset)
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
class TcpalsoNoQuestionsHandler(QnameHandler, HeaderOnlyHandler):
|
||||
qnames = ["tcpalso.no-questions."]
|
||||
flags = dns.flags.QR | dns.flags.TC
|
||||
rcode = dns.rcode.REFUSED
|
||||
|
||||
|
||||
class TruncatedNoQuestionsHandler(QnameHandler, HeaderOnlyHandler):
|
||||
qnames = ["truncated.no-questions."]
|
||||
flags = dns.flags.QR | dns.flags.AA | dns.flags.TC
|
||||
|
||||
|
||||
class FallbackHandler(HeaderOnlyHandler):
|
||||
flags = dns.flags.QR | dns.flags.AA
|
||||
|
||||
|
||||
def main() -> None:
|
||||
server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
|
||||
|
||||
# Install TCP handlers first so they take precedence
|
||||
server.install_response_handlers(
|
||||
RefusedOnTcpHandler(),
|
||||
TcpFallbackHandler(),
|
||||
)
|
||||
|
||||
# Install UDP handlers
|
||||
server.install_response_handlers(
|
||||
FormerrToAllHandler(),
|
||||
NoQuestionsNSHandler(),
|
||||
NsNoQuestionsAHandler(),
|
||||
TcpalsoNoQuestionsHandler(),
|
||||
TruncatedNoQuestionsHandler(),
|
||||
)
|
||||
server.install_response_handler(FallbackHandler())
|
||||
|
||||
server.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
144
bin/tests/system/resolver/resolver_ans.py
Normal file
144
bin/tests/system/resolver/resolver_ans.py
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
"""
|
||||
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 typing import AsyncGenerator, List, NamedTuple, Union
|
||||
|
||||
import abc
|
||||
|
||||
import dns.name
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
import dns.rrset
|
||||
|
||||
from isctest.asyncserver import (
|
||||
DnsResponseSend,
|
||||
DomainHandler,
|
||||
QnameHandler,
|
||||
QueryContext,
|
||||
)
|
||||
|
||||
|
||||
def rrset(
|
||||
qname: Union[dns.name.Name, str],
|
||||
rtype: dns.rdatatype.RdataType,
|
||||
rdata: str,
|
||||
ttl: int = 300,
|
||||
) -> dns.rrset.RRset:
|
||||
return dns.rrset.from_text(qname, ttl, dns.rdataclass.IN, rtype, rdata)
|
||||
|
||||
|
||||
def rrset_from_list(
|
||||
qname: Union[dns.name.Name, str],
|
||||
rtype: dns.rdatatype.RdataType,
|
||||
rdata_list: List[str],
|
||||
ttl: int = 300,
|
||||
) -> dns.rrset.RRset:
|
||||
return dns.rrset.from_text_list(qname, ttl, dns.rdataclass.IN, rtype, rdata_list)
|
||||
|
||||
|
||||
def soa_rrset(qname: Union[dns.name.Name, str]) -> dns.rrset.RRset:
|
||||
return rrset(qname, dns.rdatatype.SOA, ". . 0 0 0 0 0")
|
||||
|
||||
|
||||
class DelegationRRsets(NamedTuple):
|
||||
ns_rrset: dns.rrset.RRset
|
||||
a_rrset: dns.rrset.RRset
|
||||
|
||||
|
||||
def delegation_rrsets(
|
||||
owner: Union[dns.name.Name, str],
|
||||
server_number: int,
|
||||
ns_name: Union[dns.name.Name, str, None] = None,
|
||||
) -> DelegationRRsets:
|
||||
if ns_name is None:
|
||||
ns_name = f"ns.{owner}"
|
||||
ns_rrset = rrset(owner, dns.rdatatype.NS, f"{ns_name}")
|
||||
a_rrset = rrset(ns_name, dns.rdatatype.A, f"10.53.0.{server_number}")
|
||||
return DelegationRRsets(ns_rrset, a_rrset)
|
||||
|
||||
|
||||
def setup_delegation(
|
||||
qctx: QueryContext, owner: Union[dns.name.Name, str], server_number: int
|
||||
) -> None:
|
||||
delegation = delegation_rrsets(owner, server_number)
|
||||
qctx.response.authority.append(delegation.ns_rrset)
|
||||
qctx.response.additional.append(delegation.a_rrset)
|
||||
|
||||
|
||||
class DelegationHandler(DomainHandler):
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def server_number(self) -> int:
|
||||
raise NotImplementedError
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
setup_delegation(qctx, self.matched_domain, self.server_number)
|
||||
yield DnsResponseSend(qctx.response, authoritative=False)
|
||||
|
||||
|
||||
class Gl6412AHandler(QnameHandler):
|
||||
qnames = ["a.gl6412.", "a.a.gl6412."]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
qctx.response.authority.append(soa_rrset(qctx.qname))
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
class Gl6412Handler(QnameHandler):
|
||||
qnames = ["gl6412."]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
if qctx.qtype == dns.rdatatype.SOA:
|
||||
qctx.response.answer.append(soa_rrset(qctx.qname))
|
||||
elif qctx.qtype == dns.rdatatype.NS:
|
||||
ns2_rrset = rrset(qctx.qname, dns.rdatatype.NS, f"ns2.{qctx.qname}")
|
||||
ns3_rrset = rrset(qctx.qname, dns.rdatatype.NS, f"ns3.{qctx.qname}")
|
||||
qctx.response.answer.append(ns2_rrset)
|
||||
qctx.response.answer.append(ns3_rrset)
|
||||
else:
|
||||
qctx.response.authority.append(soa_rrset(qctx.qname))
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
class Gl6412Ns2Handler(QnameHandler):
|
||||
qnames = ["ns2.gl6412."]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
if qctx.qtype == dns.rdatatype.A:
|
||||
a_rrset = rrset(qctx.qname, dns.rdatatype.A, "10.53.0.2")
|
||||
qctx.response.answer.append(a_rrset)
|
||||
else:
|
||||
qctx.response.authority.append(soa_rrset(qctx.qname))
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
class Gl6412Ns3Handler(QnameHandler):
|
||||
qnames = ["ns3.gl6412."]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
if qctx.qtype == dns.rdatatype.A:
|
||||
a_rrset = rrset(qctx.qname, dns.rdatatype.A, "10.53.0.3")
|
||||
qctx.response.answer.append(a_rrset)
|
||||
else:
|
||||
qctx.response.authority.append(soa_rrset(qctx.qname))
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
import time
|
||||
|
||||
|
||||
import isctest
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import dns.rrset
|
|||
from isctest.asyncserver import (
|
||||
AsyncDnsServer,
|
||||
DnsResponseSend,
|
||||
DomainHandler,
|
||||
IgnoreAllQueries,
|
||||
QnameHandler,
|
||||
QueryContext,
|
||||
|
|
@ -28,150 +27,30 @@ from isctest.asyncserver import (
|
|||
)
|
||||
|
||||
|
||||
def setup_delegation(qctx: QueryContext, owner: str) -> None:
|
||||
ns_name = f"ns.{owner}"
|
||||
ns_rrset = dns.rrset.from_text(owner, 300, qctx.qclass, dns.rdatatype.NS, ns_name)
|
||||
a_rrset = dns.rrset.from_text(
|
||||
ns_name, 300, qctx.qclass, dns.rdatatype.A, "10.53.0.3"
|
||||
)
|
||||
qctx.response.authority.append(ns_rrset)
|
||||
qctx.response.additional.append(a_rrset)
|
||||
|
||||
|
||||
class BadGoodCnameHandler(QnameHandler):
|
||||
qnames = [
|
||||
"badcname.example.net.",
|
||||
"goodcname.example.net.",
|
||||
]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
# Data for CNAME/DNAME filtering. We need to make one-level
|
||||
# delegation to avoid automatic acceptance for subdomain aliases
|
||||
setup_delegation(qctx, "example.net.")
|
||||
yield DnsResponseSend(qctx.response, authoritative=False)
|
||||
|
||||
|
||||
class Cname1Handler(QnameHandler):
|
||||
qnames = ["cname1.example.com."]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
# Data for the "cname + other data / 1" test
|
||||
cname_rrset = dns.rrset.from_text(
|
||||
qctx.qname, 300, qctx.qclass, dns.rdatatype.CNAME, "cname1.example.com."
|
||||
)
|
||||
a_rrset = dns.rrset.from_text(
|
||||
qctx.qname, 300, qctx.qclass, dns.rdatatype.A, "1.2.3.4"
|
||||
)
|
||||
qctx.response.answer.append(cname_rrset)
|
||||
qctx.response.answer.append(a_rrset)
|
||||
yield DnsResponseSend(qctx.response, authoritative=False)
|
||||
|
||||
|
||||
class Cname2Handler(QnameHandler):
|
||||
qnames = ["cname2.example.com."]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
# Data for the "cname + other data / 2" test: same RRs in opposite order
|
||||
a_rrset = dns.rrset.from_text(
|
||||
qctx.qname, 300, qctx.qclass, dns.rdatatype.A, "1.2.3.4"
|
||||
)
|
||||
cname_rrset = dns.rrset.from_text(
|
||||
qctx.qname, 300, qctx.qclass, dns.rdatatype.CNAME, "cname2.example.com."
|
||||
)
|
||||
qctx.response.answer.append(a_rrset)
|
||||
qctx.response.answer.append(cname_rrset)
|
||||
yield DnsResponseSend(qctx.response, authoritative=False)
|
||||
|
||||
|
||||
class ExampleHandler(QnameHandler):
|
||||
qnames = [
|
||||
"www.example.com.",
|
||||
"www.example.net.",
|
||||
"badcname.example.org.",
|
||||
"goodcname.example.org.",
|
||||
"foo.badcname.example.org.",
|
||||
"foo.goodcname.example.org.",
|
||||
]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
# Data for address/alias filtering.
|
||||
if qctx.qtype == dns.rdatatype.A:
|
||||
a_rrset = dns.rrset.from_text(
|
||||
qctx.qname, 300, qctx.qclass, qctx.qtype, "192.0.2.1"
|
||||
)
|
||||
qctx.response.answer.append(a_rrset)
|
||||
elif qctx.qtype == dns.rdatatype.AAAA:
|
||||
aaaa_rrset = dns.rrset.from_text(
|
||||
qctx.qname, 300, qctx.qclass, qctx.qtype, "2001:db8:beef::1"
|
||||
)
|
||||
qctx.response.answer.append(aaaa_rrset)
|
||||
yield DnsResponseSend(qctx.response, authoritative=True)
|
||||
|
||||
|
||||
class FooInfoHandler(QnameHandler, IgnoreAllQueries):
|
||||
qnames = ["foo.info."]
|
||||
|
||||
|
||||
class NoDataHandler(DomainHandler):
|
||||
domains = ["nodata.example.net."]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
yield DnsResponseSend(qctx.response, authoritative=True)
|
||||
|
||||
|
||||
class NxdomainHandler(DomainHandler):
|
||||
domains = ["nxdomain.example.net."]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
qctx.response.set_rcode(dns.rcode.NXDOMAIN)
|
||||
yield DnsResponseSend(qctx.response, authoritative=True)
|
||||
|
||||
|
||||
class SubHandler(DomainHandler):
|
||||
domains = ["sub.example.org."]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
# Data for CNAME/DNAME filtering. The final answers are
|
||||
# expected to be accepted regardless of the filter setting.
|
||||
setup_delegation(qctx, "sub.example.org.")
|
||||
yield DnsResponseSend(qctx.response, authoritative=False)
|
||||
|
||||
|
||||
class FallbackHandler(ResponseHandler):
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
setup_delegation(qctx, "below.www.example.com.")
|
||||
yield DnsResponseSend(qctx.response, authoritative=False)
|
||||
name = "below.www.example.com."
|
||||
ns_name = f"ns.{name}"
|
||||
ns_rrset = dns.rrset.from_text(
|
||||
name, 300, qctx.qclass, dns.rdatatype.NS, ns_name
|
||||
)
|
||||
a_rrset = dns.rrset.from_text(
|
||||
ns_name, 300, qctx.qclass, dns.rdatatype.A, "10.53.0.3"
|
||||
)
|
||||
qctx.response.authority.append(ns_rrset)
|
||||
qctx.response.additional.append(a_rrset)
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
server = AsyncDnsServer(default_rcode=dns.rcode.NOERROR)
|
||||
server.install_response_handlers(
|
||||
BadGoodCnameHandler(),
|
||||
Cname1Handler(),
|
||||
Cname2Handler(),
|
||||
ExampleHandler(),
|
||||
FooInfoHandler(),
|
||||
NoDataHandler(),
|
||||
NxdomainHandler(),
|
||||
SubHandler(),
|
||||
)
|
||||
server.install_response_handler(FallbackHandler())
|
||||
server = AsyncDnsServer(default_rcode=dns.rcode.NOERROR, default_aa=False)
|
||||
server.install_response_handlers(FooInfoHandler(), FallbackHandler())
|
||||
server.run()
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue