mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-30 05:57:52 +00:00
[9.20] chg: test: Use isctest.asyncserver in the "qmin" test
Replace custom DNS servers used in the "qmin" system test with new code based on the isctest.asyncserver module. The revised code employs zone files and a limited amount of custom logic, which massively improves test readability and maintainability, extends logging, and fixes non-compliant replies sent by some of the custom servers in response to certain queries (e.g. AA=0 in authoritative empty non-terminal responses, non-glue address records in ADDITIONAL section). Backport of MR !10195 Merge branch 'backport-michal/qmin-asyncserver-9.20' into 'bind-9.20' See merge request isc-projects/bind9!10275
This commit is contained in:
commit
eaea8c751f
@ -585,7 +585,7 @@ vulture:
|
||||
<<: *precheck_job
|
||||
needs: []
|
||||
script:
|
||||
- vulture --exclude "*/ans*/ans.py,conftest.py,isctest" --ignore-names "pytestmark" bin/tests/system/
|
||||
- vulture --exclude "*ans.py,conftest.py,isctest" --ignore-names "pytestmark" bin/tests/system/
|
||||
|
||||
ci-variables:
|
||||
stage: precheck
|
||||
|
@ -308,6 +308,7 @@ def logger(request, system_test_name):
|
||||
@pytest.fixture(scope="module")
|
||||
def expected_artifacts(request):
|
||||
common_artifacts = [
|
||||
"*/.hypothesis", # drop after Ubuntu 20.04 Focal Fossa gets removed from CI
|
||||
".libs/*", # possible build artifacts, see GL #5055
|
||||
"ns*/named.conf",
|
||||
"ns*/named.memstats",
|
||||
|
17
bin/tests/system/qmin/ans2/1.0.0.2.ip6.arpa.db
Normal file
17
bin/tests/system/qmin/ans2/1.0.0.2.ip6.arpa.db
Normal file
@ -0,0 +1,17 @@
|
||||
; 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.
|
||||
|
||||
@ 30 SOA ns2.good. hostmaster.arpa. 2018050100 1 1 1 1
|
||||
@ 30 NS ns2.good.
|
||||
|
||||
8.2.6.0 60 NS ns3.good.
|
||||
|
||||
1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0 1 PTR nee.com.
|
527
bin/tests/system/qmin/ans2/ans.py
Executable file → Normal file
527
bin/tests/system/qmin/ans2/ans.py
Executable file → Normal file
@ -1,456 +1,111 @@
|
||||
# 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.
|
||||
"""
|
||||
Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
import socket
|
||||
import select
|
||||
from datetime import datetime, timedelta
|
||||
import time
|
||||
import functools
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
import dns, dns.message, dns.query, dns.flags
|
||||
from dns.rdatatype import *
|
||||
from dns.rdataclass import *
|
||||
from dns.rcode import *
|
||||
from dns.name import *
|
||||
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.message
|
||||
import dns.name
|
||||
import dns.rcode
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
|
||||
from isctest.asyncserver import (
|
||||
AsyncDnsServer,
|
||||
DnsResponseSend,
|
||||
DomainHandler,
|
||||
QueryContext,
|
||||
ResponseAction,
|
||||
)
|
||||
|
||||
from qmin_ans import (
|
||||
DelayedResponseHandler,
|
||||
EntRcodeChanger,
|
||||
QueryLogHandler,
|
||||
log_query,
|
||||
)
|
||||
|
||||
|
||||
# Log query to file
|
||||
def logquery(type, qname):
|
||||
with open("qlog", "a") as f:
|
||||
f.write("%s %s\n", type, qname)
|
||||
class QueryLogger(QueryLogHandler):
|
||||
domains = ["1.0.0.2.ip6.arpa.", "fwd.", "good."]
|
||||
|
||||
|
||||
def endswith(domain, labels):
|
||||
return domain.endswith("." + labels) or domain == labels
|
||||
class BadHandler(EntRcodeChanger):
|
||||
domains = ["bad."]
|
||||
rcode = dns.rcode.NXDOMAIN
|
||||
|
||||
|
||||
############################################################################
|
||||
# Respond to a DNS query.
|
||||
# For good. it serves:
|
||||
# ns2.good. IN A 10.53.0.2
|
||||
# zoop.boing.good. NS ns3.good.
|
||||
# ns3.good. IN A 10.53.0.3
|
||||
# too.many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.good. A 192.0.2.2
|
||||
# it responds properly (with NODATA empty response) to non-empty terminals
|
||||
#
|
||||
# For slow. it works the same as for good., but each response is delayed by 400 milliseconds
|
||||
#
|
||||
# For bad. it works the same as for good., but returns NXDOMAIN to non-empty terminals
|
||||
#
|
||||
# For ugly. it works the same as for good., but returns garbage to non-empty terminals
|
||||
#
|
||||
# For 1.0.0.2.ip6.arpa it serves
|
||||
# 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa. IN PTR nee.com.
|
||||
# 8.2.6.0.1.0.0.2.ip6.arpa IN NS ns3.good
|
||||
# 1.0.0.2.ip6.arpa. IN NS ns2.good
|
||||
# ip6.arpa. IN NS ns2.good
|
||||
#
|
||||
# For stale. it serves:
|
||||
# a.b. NS ns.a.b.stale.
|
||||
# ns.a.b.stale. IN A 10.53.0.3
|
||||
# b. NS ns.b.stale.
|
||||
# ns.b.stale. IN A 10.53.0.4
|
||||
############################################################################
|
||||
def create_response(msg):
|
||||
m = dns.message.from_wire(msg)
|
||||
qname = m.question[0].name.to_text()
|
||||
lqname = qname.lower()
|
||||
labels = lqname.split(".")
|
||||
|
||||
# get qtype
|
||||
rrtype = m.question[0].rdtype
|
||||
typename = dns.rdatatype.to_text(rrtype)
|
||||
if typename == "A" or typename == "AAAA":
|
||||
typename = "ADDR"
|
||||
bad = False
|
||||
ugly = False
|
||||
slow = False
|
||||
|
||||
# log this query
|
||||
with open("query.log", "a") as f:
|
||||
f.write("%s %s\n" % (typename, lqname))
|
||||
print("%s %s" % (typename, lqname), end=" ")
|
||||
|
||||
r = dns.message.make_response(m)
|
||||
r.set_rcode(NOERROR)
|
||||
|
||||
if endswith(lqname, "1.0.0.2.ip6.arpa."):
|
||||
# Direct query - give direct answer
|
||||
if endswith(lqname, "8.2.6.0.1.0.0.2.ip6.arpa."):
|
||||
# Delegate to ns3
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, NS, "ns3.good."
|
||||
)
|
||||
)
|
||||
r.additional.append(
|
||||
dns.rrset.from_text("ns3.good.", 60, IN, A, "10.53.0.3")
|
||||
)
|
||||
elif (
|
||||
lqname
|
||||
== "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa."
|
||||
and rrtype == PTR
|
||||
):
|
||||
# Direct query - give direct answer
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
"1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.",
|
||||
1,
|
||||
IN,
|
||||
PTR,
|
||||
"nee.com.",
|
||||
)
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "1.0.0.2.ip6.arpa." and rrtype == NS:
|
||||
# NS query at the apex
|
||||
r.answer.append(
|
||||
dns.rrset.from_text("1.0.0.2.ip6.arpa.", 30, IN, NS, "ns2.good.")
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif endswith(
|
||||
"1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.",
|
||||
lqname,
|
||||
):
|
||||
# NODATA answer
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"1.0.0.2.ip6.arpa.",
|
||||
30,
|
||||
IN,
|
||||
SOA,
|
||||
"ns2.good. hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
else:
|
||||
# NXDOMAIN
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"1.0.0.2.ip6.arpa.",
|
||||
30,
|
||||
IN,
|
||||
SOA,
|
||||
"ns2.good. hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
r.set_rcode(NXDOMAIN)
|
||||
return r
|
||||
elif endswith(lqname, "ip6.arpa."):
|
||||
if lqname == "ip6.arpa." and rrtype == NS:
|
||||
# NS query at the apex
|
||||
r.answer.append(dns.rrset.from_text("ip6.arpa.", 30, IN, NS, "ns2.good."))
|
||||
r.flags |= dns.flags.AA
|
||||
elif endswith("1.0.0.2.ip6.arpa.", lqname):
|
||||
# NODATA answer
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"ip6.arpa.",
|
||||
30,
|
||||
IN,
|
||||
SOA,
|
||||
"ns2.good. hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
else:
|
||||
# NXDOMAIN
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"ip6.arpa.",
|
||||
30,
|
||||
IN,
|
||||
SOA,
|
||||
"ns2.good. hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
r.set_rcode(NXDOMAIN)
|
||||
return r
|
||||
elif endswith(lqname, "stale."):
|
||||
if endswith(lqname, "a.b.stale."):
|
||||
# Delegate to ns.a.b.stale.
|
||||
r.authority.append(
|
||||
dns.rrset.from_text("a.b.stale.", 2, IN, NS, "ns.a.b.stale.")
|
||||
)
|
||||
r.additional.append(
|
||||
dns.rrset.from_text("ns.a.b.stale.", 2, IN, A, "10.53.0.3")
|
||||
)
|
||||
elif endswith(lqname, "b.stale."):
|
||||
# Delegate to ns.b.stale.
|
||||
r.authority.append(
|
||||
dns.rrset.from_text("b.stale.", 2, IN, NS, "ns.b.stale.")
|
||||
)
|
||||
r.additional.append(
|
||||
dns.rrset.from_text("ns.b.stale.", 2, IN, A, "10.53.0.4")
|
||||
)
|
||||
elif lqname == "stale." and rrtype == NS:
|
||||
# NS query at the apex.
|
||||
r.answer.append(dns.rrset.from_text("stale.", 2, IN, NS, "ns2.stale."))
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "stale." and rrtype == SOA:
|
||||
# SOA query at the apex.
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
"stale.", 2, IN, SOA, "ns2.stale. hostmaster.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "stale.":
|
||||
# NODATA answer
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"stale.", 2, IN, SOA, "ns2.stale. hostmaster.arpa. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "ns2.stale.":
|
||||
if rrtype == A:
|
||||
r.additional.append(
|
||||
dns.rrset.from_text("ns.b.stale.", 2, IN, A, "10.53.0.2")
|
||||
)
|
||||
else:
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"stale.", 2, IN, SOA, "ns2.stale. hostmaster.arpa. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
else:
|
||||
# NXDOMAIN
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"stale.", 2, IN, SOA, "ns2.stale. hostmaster.arpa. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
r.set_rcode(NXDOMAIN)
|
||||
return r
|
||||
elif endswith(lqname, "bad."):
|
||||
bad = True
|
||||
suffix = "bad."
|
||||
lqname = lqname[:-4]
|
||||
elif endswith(lqname, "ugly."):
|
||||
ugly = True
|
||||
suffix = "ugly."
|
||||
lqname = lqname[:-5]
|
||||
elif endswith(lqname, "good."):
|
||||
suffix = "good."
|
||||
lqname = lqname[:-5]
|
||||
elif endswith(lqname, "slow."):
|
||||
slow = True
|
||||
suffix = "slow."
|
||||
lqname = lqname[:-5]
|
||||
elif endswith(lqname, "fwd."):
|
||||
suffix = "fwd."
|
||||
lqname = lqname[:-4]
|
||||
else:
|
||||
r.set_rcode(REFUSED)
|
||||
return r
|
||||
|
||||
# Good/bad/ugly differs only in how we treat non-empty terminals
|
||||
if endswith(lqname, "zoop.boing."):
|
||||
r.authority.append(
|
||||
dns.rrset.from_text("zoop.boing." + suffix, 1, IN, NS, "ns3." + suffix)
|
||||
)
|
||||
elif (
|
||||
lqname == "many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z."
|
||||
and rrtype == A
|
||||
):
|
||||
r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, A, "192.0.2.2"))
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "" and rrtype == NS:
|
||||
r.answer.append(dns.rrset.from_text(suffix, 30, IN, NS, "ns2." + suffix))
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "ns2.":
|
||||
r.flags |= dns.flags.AA
|
||||
if rrtype == A:
|
||||
r.answer.append(
|
||||
dns.rrset.from_text("ns2." + suffix, 30, IN, A, "10.53.0.2")
|
||||
)
|
||||
elif rrtype == AAAA:
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
"ns2." + suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::2"
|
||||
)
|
||||
)
|
||||
else:
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
suffix,
|
||||
30,
|
||||
IN,
|
||||
SOA,
|
||||
"ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
elif lqname == "ns3.":
|
||||
r.flags |= dns.flags.AA
|
||||
if rrtype == A:
|
||||
r.answer.append(
|
||||
dns.rrset.from_text("ns3." + suffix, 30, IN, A, "10.53.0.3")
|
||||
)
|
||||
elif lqname == "ns3." and rrtype == AAAA:
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
"ns3." + suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::3"
|
||||
)
|
||||
)
|
||||
else:
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
suffix,
|
||||
30,
|
||||
IN,
|
||||
SOA,
|
||||
"ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
elif lqname == "ns4.":
|
||||
r.flags |= dns.flags.AA
|
||||
if rrtype == A:
|
||||
r.answer.append(
|
||||
dns.rrset.from_text("ns4." + suffix, 30, IN, A, "10.53.0.4")
|
||||
)
|
||||
elif rrtype == AAAA:
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
"ns4." + suffix, 30, IN, AAAA, "fd92:7065:b8e:ffff::4"
|
||||
)
|
||||
)
|
||||
else:
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
suffix,
|
||||
30,
|
||||
IN,
|
||||
SOA,
|
||||
"ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
elif lqname == "a.bit.longer.ns.name." and rrtype == A:
|
||||
r.answer.append(
|
||||
dns.rrset.from_text("a.bit.longer.ns.name." + suffix, 1, IN, A, "10.53.0.4")
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "a.bit.longer.ns.name." and rrtype == AAAA:
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
"a.bit.longer.ns.name." + suffix, 1, IN, AAAA, "fd92:7065:b8e:ffff::4"
|
||||
)
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
else:
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
suffix,
|
||||
1,
|
||||
IN,
|
||||
SOA,
|
||||
"ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
if bad or not (
|
||||
endswith("icky.icky.icky.ptang.zoop.boing.", lqname)
|
||||
or endswith(
|
||||
"many.labels.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.",
|
||||
lqname,
|
||||
)
|
||||
or endswith("a.bit.longer.ns.name.", lqname)
|
||||
):
|
||||
r.set_rcode(NXDOMAIN)
|
||||
if ugly:
|
||||
r.set_rcode(FORMERR)
|
||||
if slow:
|
||||
time.sleep(0.2)
|
||||
return r
|
||||
class UglyHandler(EntRcodeChanger):
|
||||
domains = ["ugly."]
|
||||
rcode = dns.rcode.FORMERR
|
||||
|
||||
|
||||
def sigterm(signum, frame):
|
||||
print("Shutting down now...")
|
||||
os.remove("ans.pid")
|
||||
running = False
|
||||
sys.exit(0)
|
||||
class SlowHandler(DelayedResponseHandler):
|
||||
domains = ["slow."]
|
||||
delay = 0.2
|
||||
|
||||
|
||||
############################################################################
|
||||
# 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.2"
|
||||
ip6 = "fd92:7065:b8e:ffff::2"
|
||||
def send_delegation(
|
||||
qctx: QueryContext, zone_cut: dns.name.Name, target_addr: str
|
||||
) -> ResponseAction:
|
||||
"""
|
||||
Delegate `zone_cut` to a single in-bailiwick name server, `ns.<zone_cut>`,
|
||||
with a single IPv4 glue record (provided in `target_addr`) included in the
|
||||
ADDITIONAL section.
|
||||
"""
|
||||
ns_name = "ns." + zone_cut.to_text()
|
||||
ns_rrset = dns.rrset.from_text(
|
||||
zone_cut, 2, dns.rdataclass.IN, dns.rdatatype.NS, ns_name
|
||||
)
|
||||
a_rrset = dns.rrset.from_text(
|
||||
ns_name, 2, dns.rdataclass.IN, dns.rdatatype.A, target_addr
|
||||
)
|
||||
|
||||
try:
|
||||
port = int(os.environ["PORT"])
|
||||
except:
|
||||
port = 5300
|
||||
response = dns.message.make_response(qctx.query)
|
||||
response.set_rcode(dns.rcode.NOERROR)
|
||||
response.authority.append(ns_rrset)
|
||||
response.additional.append(a_rrset)
|
||||
|
||||
query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
query4_socket.bind((ip4, port))
|
||||
return DnsResponseSend(response, authoritative=False)
|
||||
|
||||
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)
|
||||
class StaleHandler(DomainHandler):
|
||||
"""
|
||||
`a.b.stale` is a subdomain of `b.stale` and these two subdomains need to be
|
||||
delegated to different name servers. Therefore, their delegations cannot
|
||||
be placed in the zone file because the zone cut at `b.stale` would occlude
|
||||
the one at `a.b.stale`. Generate these delegations dynamically depending
|
||||
on the QNAME.
|
||||
"""
|
||||
|
||||
f = open("ans.pid", "w")
|
||||
pid = os.getpid()
|
||||
print(pid, file=f)
|
||||
f.close()
|
||||
domains = ["stale."]
|
||||
|
||||
running = True
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[ResponseAction, None]:
|
||||
log_query(qctx)
|
||||
a_b_stale = dns.name.from_text("a.b.stale.")
|
||||
b_stale = dns.name.from_text("b.stale.")
|
||||
if qctx.qname.is_subdomain(a_b_stale):
|
||||
yield send_delegation(qctx, a_b_stale, "10.53.0.3")
|
||||
elif qctx.qname.is_subdomain(b_stale):
|
||||
yield send_delegation(qctx, b_stale, "10.53.0.4")
|
||||
|
||||
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__":
|
||||
server = AsyncDnsServer()
|
||||
server.install_response_handler(QueryLogger())
|
||||
server.install_response_handler(BadHandler())
|
||||
server.install_response_handler(UglyHandler())
|
||||
server.install_response_handler(SlowHandler())
|
||||
server.install_response_handler(StaleHandler())
|
||||
server.run()
|
||||
|
1
bin/tests/system/qmin/ans2/bad.db
Symbolic link
1
bin/tests/system/qmin/ans2/bad.db
Symbolic link
@ -0,0 +1 @@
|
||||
good.db
|
1
bin/tests/system/qmin/ans2/fwd.db
Symbolic link
1
bin/tests/system/qmin/ans2/fwd.db
Symbolic link
@ -0,0 +1 @@
|
||||
good.db
|
26
bin/tests/system/qmin/ans2/good.db
Normal file
26
bin/tests/system/qmin/ans2/good.db
Normal file
@ -0,0 +1,26 @@
|
||||
; 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.
|
||||
|
||||
@ 1 SOA ns2 hostmaster.arpa. 2018050100 1 1 1 1
|
||||
|
||||
@ 30 NS ns2
|
||||
ns2 30 A 10.53.0.2
|
||||
30 AAAA fd92:7065:b8e:ffff::2
|
||||
|
||||
zoop.boing 30 NS ns3
|
||||
ns3 30 A 10.53.0.3
|
||||
30 AAAA fd92:7065:b8e:ffff::3
|
||||
|
||||
ns4 30 A 10.53.0.4
|
||||
30 AAAA fd92:7065:b8e:ffff::4
|
||||
|
||||
a.bit.longer.ns.name 1 A 10.53.0.4
|
||||
1 AAAA fd92:7065:b8e:ffff::4
|
1
bin/tests/system/qmin/ans2/slow.db
Symbolic link
1
bin/tests/system/qmin/ans2/slow.db
Symbolic link
@ -0,0 +1 @@
|
||||
good.db
|
15
bin/tests/system/qmin/ans2/stale.db
Normal file
15
bin/tests/system/qmin/ans2/stale.db
Normal file
@ -0,0 +1,15 @@
|
||||
; 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.
|
||||
|
||||
@ 2 SOA ns2 hostmaster.stale. 1 2 3 4 5
|
||||
@ 2 NS ns2
|
||||
ns2 2 A 10.53.0.2
|
||||
2 AAAA fd92:7065:b8e:ffff::2
|
1
bin/tests/system/qmin/ans2/ugly.db
Symbolic link
1
bin/tests/system/qmin/ans2/ugly.db
Symbolic link
@ -0,0 +1 @@
|
||||
good.db
|
15
bin/tests/system/qmin/ans3/8.2.6.0.1.0.0.2.ip6.arpa.db
Normal file
15
bin/tests/system/qmin/ans3/8.2.6.0.1.0.0.2.ip6.arpa.db
Normal file
@ -0,0 +1,15 @@
|
||||
; 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.
|
||||
|
||||
@ 30 SOA ns3.good. hostmaster.arpa. 2018050100 1 1 1 1
|
||||
@ 30 NS ns3.good.
|
||||
|
||||
1.1.1.1 60 NS ns4.good.
|
15
bin/tests/system/qmin/ans3/a.b.stale.db
Normal file
15
bin/tests/system/qmin/ans3/a.b.stale.db
Normal file
@ -0,0 +1,15 @@
|
||||
; 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.
|
||||
|
||||
@ 1 SOA ns hostmaster.a.b.stale. 1 2 3 4 5
|
||||
@ 1 NS ns
|
||||
@ 1 TXT "peekaboo"
|
||||
ns 1 A 10.53.0.3
|
307
bin/tests/system/qmin/ans3/ans.py
Executable file → Normal file
307
bin/tests/system/qmin/ans3/ans.py
Executable file → Normal file
@ -1,285 +1,46 @@
|
||||
# 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.
|
||||
"""
|
||||
Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
import socket
|
||||
import select
|
||||
from datetime import datetime, timedelta
|
||||
import time
|
||||
import functools
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
import dns, dns.message, dns.query, dns.flags
|
||||
from dns.rdatatype import *
|
||||
from dns.rdataclass import *
|
||||
from dns.rcode import *
|
||||
from dns.name import *
|
||||
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 dns.rcode
|
||||
|
||||
from isctest.asyncserver import AsyncDnsServer
|
||||
|
||||
from qmin_ans import DelayedResponseHandler, EntRcodeChanger, QueryLogHandler
|
||||
|
||||
|
||||
# Log query to file
|
||||
def logquery(type, qname):
|
||||
with open("qlog", "a") as f:
|
||||
f.write("%s %s\n", type, qname)
|
||||
class QueryLogger(QueryLogHandler):
|
||||
domains = ["8.2.6.0.1.0.0.2.ip6.arpa.", "a.b.stale.", "zoop.boing.good."]
|
||||
|
||||
|
||||
def endswith(domain, labels):
|
||||
return domain.endswith("." + labels) or domain == labels
|
||||
class ZoopBoingBadHandler(EntRcodeChanger):
|
||||
domains = ["zoop.boing.bad."]
|
||||
rcode = dns.rcode.NXDOMAIN
|
||||
|
||||
|
||||
############################################################################
|
||||
# Respond to a DNS query.
|
||||
# For good. it serves:
|
||||
# zoop.boing.good. NS ns3.good.
|
||||
# icky.ptang.zoop.boing.good. NS a.bit.longer.ns.name.good.
|
||||
# it responds properly (with NODATA empty response) to non-empty terminals
|
||||
#
|
||||
# For slow. it works the same as for good., but each response is delayed by 400 milliseconds
|
||||
#
|
||||
# For bad. it works the same as for good., but returns NXDOMAIN to non-empty terminals
|
||||
#
|
||||
# For ugly. it works the same as for good., but returns garbage to non-empty terminals
|
||||
#
|
||||
# For stale. it serves:
|
||||
# a.b.stale. IN TXT peekaboo (resolver did not do qname minimization)
|
||||
############################################################################
|
||||
def create_response(msg):
|
||||
m = dns.message.from_wire(msg)
|
||||
qname = m.question[0].name.to_text()
|
||||
lqname = qname.lower()
|
||||
labels = lqname.split(".")
|
||||
suffix = ""
|
||||
|
||||
# get qtype
|
||||
rrtype = m.question[0].rdtype
|
||||
typename = dns.rdatatype.to_text(rrtype)
|
||||
if typename == "A" or typename == "AAAA":
|
||||
typename = "ADDR"
|
||||
bad = False
|
||||
ugly = False
|
||||
slow = False
|
||||
|
||||
# log this query
|
||||
with open("query.log", "a") as f:
|
||||
f.write("%s %s\n" % (typename, lqname))
|
||||
print("%s %s" % (typename, lqname), end=" ")
|
||||
|
||||
r = dns.message.make_response(m)
|
||||
r.set_rcode(NOERROR)
|
||||
|
||||
ip6req = False
|
||||
|
||||
if endswith(lqname, "bad."):
|
||||
bad = True
|
||||
suffix = "bad."
|
||||
lqname = lqname[:-4]
|
||||
elif endswith(lqname, "ugly."):
|
||||
ugly = True
|
||||
suffix = "ugly."
|
||||
lqname = lqname[:-5]
|
||||
elif endswith(lqname, "good."):
|
||||
suffix = "good."
|
||||
lqname = lqname[:-5]
|
||||
elif endswith(lqname, "slow."):
|
||||
slow = True
|
||||
suffix = "slow."
|
||||
lqname = lqname[:-5]
|
||||
elif endswith(lqname, "8.2.6.0.1.0.0.2.ip6.arpa."):
|
||||
ip6req = True
|
||||
elif endswith(lqname, "a.b.stale."):
|
||||
if lqname == "a.b.stale.":
|
||||
r.flags |= dns.flags.AA
|
||||
if rrtype == TXT:
|
||||
# Direct query.
|
||||
r.answer.append(dns.rrset.from_text(lqname, 1, IN, TXT, "peekaboo"))
|
||||
elif rrtype == NS:
|
||||
# NS a.b.
|
||||
r.answer.append(dns.rrset.from_text(lqname, 1, IN, NS, "ns.a.b.stale."))
|
||||
r.additional.append(
|
||||
dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.3")
|
||||
)
|
||||
elif rrtype == SOA:
|
||||
# SOA a.b.
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
else:
|
||||
# NODATA.
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
elif lqname == "ns.a.b.stale.":
|
||||
r.flags |= dns.flags.AA
|
||||
if rrtype == A:
|
||||
r.answer.append(
|
||||
dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.3")
|
||||
)
|
||||
else:
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
else:
|
||||
r.flags |= dns.flags.AA
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
r.set_rcode(NXDOMAIN)
|
||||
# NXDOMAIN.
|
||||
return r
|
||||
else:
|
||||
r.set_rcode(REFUSED)
|
||||
return r
|
||||
|
||||
# Good/bad differs only in how we treat non-empty terminals
|
||||
if lqname == "zoop.boing." and rrtype == NS:
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(lqname + suffix, 1, IN, NS, "ns3." + suffix)
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif endswith(lqname, "icky.ptang.zoop.boing."):
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"icky.ptang.zoop.boing." + suffix,
|
||||
1,
|
||||
IN,
|
||||
NS,
|
||||
"a.bit.longer.ns.name." + suffix,
|
||||
)
|
||||
)
|
||||
elif endswith("icky.ptang.zoop.boing.", lqname):
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"zoop.boing." + suffix,
|
||||
1,
|
||||
IN,
|
||||
SOA,
|
||||
"ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
if bad:
|
||||
r.set_rcode(NXDOMAIN)
|
||||
if ugly:
|
||||
r.set_rcode(FORMERR)
|
||||
elif endswith(lqname, "zoop.boing."):
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"zoop.boing." + suffix,
|
||||
1,
|
||||
IN,
|
||||
SOA,
|
||||
"ns3." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
r.set_rcode(NXDOMAIN)
|
||||
elif ip6req:
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.", 60, IN, NS, "ns4.good."
|
||||
)
|
||||
)
|
||||
r.additional.append(dns.rrset.from_text("ns4.good.", 60, IN, A, "10.53.0.4"))
|
||||
else:
|
||||
r.set_rcode(REFUSED)
|
||||
|
||||
if slow:
|
||||
time.sleep(0.4)
|
||||
return r
|
||||
class ZoopBoingUglyHandler(EntRcodeChanger):
|
||||
domains = ["zoop.boing.ugly."]
|
||||
rcode = dns.rcode.FORMERR
|
||||
|
||||
|
||||
def sigterm(signum, frame):
|
||||
print("Shutting down now...")
|
||||
os.remove("ans.pid")
|
||||
running = False
|
||||
sys.exit(0)
|
||||
class ZoopBoingSlowHandler(DelayedResponseHandler):
|
||||
domains = ["zoop.boing.slow."]
|
||||
delay = 0.4
|
||||
|
||||
|
||||
############################################################################
|
||||
# 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.3"
|
||||
ip6 = "fd92:7065:b8e:ffff::3"
|
||||
|
||||
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__":
|
||||
server = AsyncDnsServer()
|
||||
server.install_response_handler(QueryLogger())
|
||||
server.install_response_handler(ZoopBoingBadHandler())
|
||||
server.install_response_handler(ZoopBoingUglyHandler())
|
||||
server.install_response_handler(ZoopBoingSlowHandler())
|
||||
server.run()
|
||||
|
14
bin/tests/system/qmin/ans3/zoop.boing.bad.db
Normal file
14
bin/tests/system/qmin/ans3/zoop.boing.bad.db
Normal file
@ -0,0 +1,14 @@
|
||||
; 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.
|
||||
|
||||
@ 1 SOA ns3.bad. hostmaster.arpa. 2018050100 1 1 1 1
|
||||
@ 1 NS ns3.bad.
|
||||
icky.ptang 1 NS a.bit.longer.ns.name.bad.
|
14
bin/tests/system/qmin/ans3/zoop.boing.good.db
Normal file
14
bin/tests/system/qmin/ans3/zoop.boing.good.db
Normal file
@ -0,0 +1,14 @@
|
||||
; 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.
|
||||
|
||||
@ 1 SOA ns3.good. hostmaster.arpa. 2018050100 1 1 1 1
|
||||
@ 1 NS ns3.good.
|
||||
icky.ptang 1 NS a.bit.longer.ns.name.good.
|
14
bin/tests/system/qmin/ans3/zoop.boing.slow.db
Normal file
14
bin/tests/system/qmin/ans3/zoop.boing.slow.db
Normal file
@ -0,0 +1,14 @@
|
||||
; 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.
|
||||
|
||||
@ 1 SOA ns3.slow. hostmaster.arpa. 2018050100 1 1 1 1
|
||||
@ 1 NS ns3.slow.
|
||||
icky.ptang 1 NS a.bit.longer.ns.name.slow.
|
14
bin/tests/system/qmin/ans3/zoop.boing.ugly.db
Normal file
14
bin/tests/system/qmin/ans3/zoop.boing.ugly.db
Normal file
@ -0,0 +1,14 @@
|
||||
; 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.
|
||||
|
||||
@ 1 SOA ns3.ugly. hostmaster.arpa. 2018050100 1 1 1 1
|
||||
@ 1 NS ns3.ugly.
|
||||
icky.ptang 1 NS a.bit.longer.ns.name.ugly.
|
@ -0,0 +1,15 @@
|
||||
; 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.
|
||||
|
||||
@ 30 SOA ns4.good. hostmaster.arpa. 2018050100 1 1 1 1
|
||||
@ 30 NS ns4.good.
|
||||
|
||||
test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4 1 TXT "long_ip6_name"
|
15
bin/tests/system/qmin/ans4/a.b.stale.db
Normal file
15
bin/tests/system/qmin/ans4/a.b.stale.db
Normal file
@ -0,0 +1,15 @@
|
||||
; 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.
|
||||
|
||||
@ 1 SOA ns hostmaster.a.b.stale. 1 2 3 4 5
|
||||
@ 1 NS ns
|
||||
ns 1 A 10.53.0.4
|
||||
@ 1 TXT "hooray"
|
409
bin/tests/system/qmin/ans4/ans.py
Executable file → Normal file
409
bin/tests/system/qmin/ans4/ans.py
Executable file → Normal file
@ -1,344 +1,93 @@
|
||||
# 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.
|
||||
"""
|
||||
Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
import socket
|
||||
import select
|
||||
from datetime import datetime, timedelta
|
||||
import time
|
||||
import functools
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
import dns, dns.message, dns.query, dns.flags
|
||||
from dns.rdatatype import *
|
||||
from dns.rdataclass import *
|
||||
from dns.rcode import *
|
||||
from dns.name import *
|
||||
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.rcode
|
||||
|
||||
from isctest.asyncserver import (
|
||||
AsyncDnsServer,
|
||||
DnsResponseSend,
|
||||
DomainHandler,
|
||||
QueryContext,
|
||||
ResponseAction,
|
||||
)
|
||||
|
||||
from qmin_ans import DelayedResponseHandler, EntRcodeChanger, QueryLogHandler, log_query
|
||||
|
||||
|
||||
# Log query to file
|
||||
def logquery(type, qname):
|
||||
with open("qlog", "a") as f:
|
||||
f.write("%s %s\n", type, qname)
|
||||
class QueryLogger(QueryLogHandler):
|
||||
domains = [
|
||||
"1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
|
||||
"icky.ptang.zoop.boing.good.",
|
||||
]
|
||||
|
||||
|
||||
def endswith(domain, labels):
|
||||
return domain.endswith("." + labels) or domain == labels
|
||||
class StaleHandler(DomainHandler):
|
||||
"""
|
||||
The test code relies on this server returning non-minimal (i.e. including
|
||||
address records in the ADDITIONAL section) responses to NS queries for
|
||||
`b.stale` and `a.b.stale`. While this logic (returning non-minimal
|
||||
responses to NS queries) could be implemented in AsyncDnsServer itself,
|
||||
doing so breaks a lot of other checks in this system test. Therefore, only
|
||||
these two zones behave in this particular way, thanks to a custom response
|
||||
handler implemented below.
|
||||
"""
|
||||
|
||||
domains = ["b.stale", "a.b.stale"]
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[ResponseAction, None]:
|
||||
log_query(qctx)
|
||||
|
||||
if qctx.qtype == dns.rdatatype.NS:
|
||||
assert qctx.zone
|
||||
assert qctx.response.answer[0]
|
||||
|
||||
for nameserver in qctx.response.answer[0]:
|
||||
if not nameserver.target.is_subdomain(qctx.response.answer[0].name):
|
||||
continue
|
||||
glue_a = qctx.zone.get_rrset(nameserver.target, dns.rdatatype.A)
|
||||
if glue_a:
|
||||
qctx.response.additional.append(glue_a)
|
||||
glue_aaaa = qctx.zone.get_rrset(nameserver.target, dns.rdatatype.AAAA)
|
||||
if glue_aaaa:
|
||||
qctx.response.additional.append(glue_aaaa)
|
||||
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
############################################################################
|
||||
# Respond to a DNS query.
|
||||
# For good. it serves:
|
||||
# icky.ptang.zoop.boing.good. NS a.bit.longer.ns.name.
|
||||
# icky.icky.icky.ptang.zoop.boing.good. A 192.0.2.1
|
||||
# more.icky.icky.icky.ptang.zoop.boing.good. A 192.0.2.2
|
||||
# it responds properly (with NODATA empty response) to non-empty terminals
|
||||
#
|
||||
# For slow. it works the same as for good., but each response is delayed by 400 milliseconds
|
||||
#
|
||||
# For bad. it works the same as for good., but returns NXDOMAIN to non-empty terminals
|
||||
#
|
||||
# For ugly. it works the same as for good., but returns garbage to non-empty terminals
|
||||
#
|
||||
# For stale. it serves:
|
||||
# a.b.stale. IN TXT hooray (resolver did do qname minimization)
|
||||
############################################################################
|
||||
def create_response(msg):
|
||||
m = dns.message.from_wire(msg)
|
||||
qname = m.question[0].name.to_text()
|
||||
lqname = qname.lower()
|
||||
labels = lqname.split(".")
|
||||
suffix = ""
|
||||
|
||||
# get qtype
|
||||
rrtype = m.question[0].rdtype
|
||||
typename = dns.rdatatype.to_text(rrtype)
|
||||
if typename == "A" or typename == "AAAA":
|
||||
typename = "ADDR"
|
||||
bad = False
|
||||
slow = False
|
||||
ugly = False
|
||||
|
||||
# log this query
|
||||
with open("query.log", "a") as f:
|
||||
f.write("%s %s\n" % (typename, lqname))
|
||||
print("%s %s" % (typename, lqname), end=" ")
|
||||
|
||||
r = dns.message.make_response(m)
|
||||
r.set_rcode(NOERROR)
|
||||
|
||||
ip6req = False
|
||||
|
||||
if endswith(lqname, "bad."):
|
||||
bad = True
|
||||
suffix = "bad."
|
||||
lqname = lqname[:-4]
|
||||
elif endswith(lqname, "ugly."):
|
||||
ugly = True
|
||||
suffix = "ugly."
|
||||
lqname = lqname[:-5]
|
||||
elif endswith(lqname, "good."):
|
||||
suffix = "good."
|
||||
lqname = lqname[:-5]
|
||||
elif endswith(lqname, "slow."):
|
||||
slow = True
|
||||
suffix = "slow."
|
||||
lqname = lqname[:-5]
|
||||
elif endswith(lqname, "1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa."):
|
||||
ip6req = True
|
||||
elif endswith(lqname, "b.stale."):
|
||||
if lqname == "a.b.stale.":
|
||||
r.flags |= dns.flags.AA
|
||||
if rrtype == TXT:
|
||||
# Direct query.
|
||||
r.answer.append(dns.rrset.from_text(lqname, 1, IN, TXT, "hooray"))
|
||||
elif rrtype == NS:
|
||||
# NS a.b.
|
||||
r.answer.append(dns.rrset.from_text(lqname, 1, IN, NS, "ns.a.b.stale."))
|
||||
r.additional.append(
|
||||
dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.3")
|
||||
)
|
||||
elif rrtype == SOA:
|
||||
# SOA a.b.
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
else:
|
||||
# NODATA.
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
elif lqname == "ns.a.b.stale.":
|
||||
r.flags |= dns.flags.AA
|
||||
if rrtype == A:
|
||||
r.answer.append(
|
||||
dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.3")
|
||||
)
|
||||
else:
|
||||
# NODATA.
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "a.b.stale. hostmaster.a.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
elif lqname == "b.stale.":
|
||||
r.flags |= dns.flags.AA
|
||||
if rrtype == NS:
|
||||
# NS b.
|
||||
r.answer.append(dns.rrset.from_text(lqname, 1, IN, NS, "ns.b.stale."))
|
||||
r.additional.append(
|
||||
dns.rrset.from_text("ns.b.stale.", 1, IN, A, "10.53.0.4")
|
||||
)
|
||||
elif rrtype == SOA:
|
||||
# SOA b.
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
else:
|
||||
# NODATA.
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
elif lqname == "ns.b.stale.":
|
||||
r.flags |= dns.flags.AA
|
||||
if rrtype == A:
|
||||
# SOA a.b.
|
||||
r.answer.append(
|
||||
dns.rrset.from_text("ns.a.b.stale.", 1, IN, A, "10.53.0.4")
|
||||
)
|
||||
else:
|
||||
# NODATA.
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
else:
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
lqname, 1, IN, SOA, "b.stale. hostmaster.b.stale. 1 2 3 4 5"
|
||||
)
|
||||
)
|
||||
r.set_rcode(NXDOMAIN)
|
||||
# NXDOMAIN.
|
||||
return r
|
||||
else:
|
||||
r.set_rcode(REFUSED)
|
||||
return r
|
||||
|
||||
# Good/bad differs only in how we treat non-empty terminals
|
||||
if lqname == "icky.icky.icky.ptang.zoop.boing." and rrtype == A:
|
||||
r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, A, "192.0.2.1"))
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "more.icky.icky.icky.ptang.zoop.boing." and rrtype == A:
|
||||
r.answer.append(dns.rrset.from_text(lqname + suffix, 1, IN, A, "192.0.2.2"))
|
||||
r.flags |= dns.flags.AA
|
||||
elif lqname == "icky.ptang.zoop.boing." and rrtype == NS:
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
lqname + suffix, 1, IN, NS, "a.bit.longer.ns.name." + suffix
|
||||
)
|
||||
)
|
||||
r.flags |= dns.flags.AA
|
||||
elif endswith(lqname, "icky.ptang.zoop.boing."):
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"icky.ptang.zoop.boing." + suffix,
|
||||
1,
|
||||
IN,
|
||||
SOA,
|
||||
"ns2." + suffix + " hostmaster.arpa. 2018050100 1 1 1 1",
|
||||
)
|
||||
)
|
||||
if bad or not endswith("more.icky.icky.icky.ptang.zoop.boing.", lqname):
|
||||
r.set_rcode(NXDOMAIN)
|
||||
if ugly:
|
||||
r.set_rcode(FORMERR)
|
||||
elif ip6req:
|
||||
r.flags |= dns.flags.AA
|
||||
if (
|
||||
lqname
|
||||
== "test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa."
|
||||
and rrtype == TXT
|
||||
):
|
||||
r.answer.append(
|
||||
dns.rrset.from_text(
|
||||
"test1.test2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
|
||||
1,
|
||||
IN,
|
||||
TXT,
|
||||
"long_ip6_name",
|
||||
)
|
||||
)
|
||||
elif endswith(
|
||||
"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.0.9.4.1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
|
||||
lqname,
|
||||
):
|
||||
# NODATA answer
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
|
||||
60,
|
||||
IN,
|
||||
SOA,
|
||||
"ns4.good. hostmaster.arpa. 2018050100 120 30 320 16",
|
||||
)
|
||||
)
|
||||
else:
|
||||
# NXDOMAIN
|
||||
r.authority.append(
|
||||
dns.rrset.from_text(
|
||||
"1.1.1.1.8.2.6.0.1.0.0.2.ip6.arpa.",
|
||||
60,
|
||||
IN,
|
||||
SOA,
|
||||
"ns4.good. hostmaster.arpa. 2018050100 120 30 320 16",
|
||||
)
|
||||
)
|
||||
r.set_rcode(NXDOMAIN)
|
||||
else:
|
||||
r.set_rcode(REFUSED)
|
||||
|
||||
if slow:
|
||||
time.sleep(0.4)
|
||||
return r
|
||||
class IckyPtangZoopBoingBadHandler(EntRcodeChanger):
|
||||
domains = ["icky.ptang.zoop.boing.bad."]
|
||||
rcode = dns.rcode.NXDOMAIN
|
||||
|
||||
|
||||
def sigterm(signum, frame):
|
||||
print("Shutting down now...")
|
||||
os.remove("ans.pid")
|
||||
running = False
|
||||
sys.exit(0)
|
||||
class IckyPtangZoopBoingUglyHandler(EntRcodeChanger):
|
||||
domains = ["icky.ptang.zoop.boing.ugly."]
|
||||
rcode = dns.rcode.FORMERR
|
||||
|
||||
|
||||
############################################################################
|
||||
# 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.4"
|
||||
ip6 = "fd92:7065:b8e:ffff::4"
|
||||
class IckyPtangZoopBoingSlowHandler(DelayedResponseHandler):
|
||||
domains = ["icky.ptang.zoop.boing.slow."]
|
||||
delay = 0.4
|
||||
|
||||
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__":
|
||||
server = AsyncDnsServer()
|
||||
server.install_response_handler(QueryLogger())
|
||||
server.install_response_handler(StaleHandler())
|
||||
server.install_response_handler(IckyPtangZoopBoingBadHandler())
|
||||
server.install_response_handler(IckyPtangZoopBoingUglyHandler())
|
||||
server.install_response_handler(IckyPtangZoopBoingSlowHandler())
|
||||
server.run()
|
||||
|
16
bin/tests/system/qmin/ans4/b.stale.db
Normal file
16
bin/tests/system/qmin/ans4/b.stale.db
Normal file
@ -0,0 +1,16 @@
|
||||
; 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.
|
||||
|
||||
@ 1 SOA ns hostmaster.b.stale. 1 2 3 4 5
|
||||
@ 1 NS ns
|
||||
ns 1 A 10.53.0.4
|
||||
a 1 NS ns.a
|
||||
ns.a 1 A 10.53.0.4
|
15
bin/tests/system/qmin/ans4/icky.ptang.zoop.boing.bad.db
Normal file
15
bin/tests/system/qmin/ans4/icky.ptang.zoop.boing.bad.db
Normal file
@ -0,0 +1,15 @@
|
||||
; 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.
|
||||
|
||||
@ 1 SOA ns4.bad. hostmaster.arpa. 2018050100 1 1 1 1
|
||||
@ 1 NS a.bit.longer.ns.name.bad.
|
||||
icky.icky 1 A 192.0.2.1
|
||||
more.icky.icky 1 A 192.0.2.2
|
15
bin/tests/system/qmin/ans4/icky.ptang.zoop.boing.good.db
Normal file
15
bin/tests/system/qmin/ans4/icky.ptang.zoop.boing.good.db
Normal file
@ -0,0 +1,15 @@
|
||||
; 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.
|
||||
|
||||
@ 1 SOA ns4.good. hostmaster.arpa. 2018050100 1 1 1 1
|
||||
@ 1 NS a.bit.longer.ns.name.good.
|
||||
icky.icky 1 A 192.0.2.1
|
||||
more.icky.icky 1 A 192.0.2.2
|
15
bin/tests/system/qmin/ans4/icky.ptang.zoop.boing.slow.db
Normal file
15
bin/tests/system/qmin/ans4/icky.ptang.zoop.boing.slow.db
Normal file
@ -0,0 +1,15 @@
|
||||
; 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.
|
||||
|
||||
@ 1 SOA ns4.slow. hostmaster.arpa. 2018050100 1 1 1 1
|
||||
@ 1 NS a.bit.longer.ns.name.slow.
|
||||
icky.icky 1 A 192.0.2.1
|
||||
more.icky.icky 1 A 192.0.2.2
|
15
bin/tests/system/qmin/ans4/icky.ptang.zoop.boing.ugly.db
Normal file
15
bin/tests/system/qmin/ans4/icky.ptang.zoop.boing.ugly.db
Normal file
@ -0,0 +1,15 @@
|
||||
; 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.
|
||||
|
||||
@ 1 SOA ns4.ugly. hostmaster.arpa. 2018050100 1 1 1 1
|
||||
@ 1 NS a.bit.longer.ns.name.ugly.
|
||||
icky.icky 1 A 192.0.2.1
|
||||
more.icky.icky 1 A 192.0.2.2
|
107
bin/tests/system/qmin/qmin_ans.py
Normal file
107
bin/tests/system/qmin/qmin_ans.py
Normal file
@ -0,0 +1,107 @@
|
||||
"""
|
||||
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 abc
|
||||
|
||||
import dns.rcode
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
|
||||
from isctest.asyncserver import (
|
||||
DnsResponseSend,
|
||||
DomainHandler,
|
||||
QueryContext,
|
||||
ResponseAction,
|
||||
)
|
||||
|
||||
from isctest.compat import dns_rcode
|
||||
|
||||
|
||||
def log_query(qctx: QueryContext) -> None:
|
||||
"""
|
||||
Log a received DNS query to a text file inspected by `tests.sh`. AAAA and
|
||||
A queries are logged identically because the relative order in which they
|
||||
are received does not matter.
|
||||
"""
|
||||
qname = qctx.qname.to_text()
|
||||
qtype = dns.rdatatype.to_text(qctx.qtype)
|
||||
if qtype in ("A", "AAAA"):
|
||||
qtype = "ADDR"
|
||||
|
||||
with open("query.log", "a", encoding="utf-8") as query_log:
|
||||
print(f"{qtype} {qname}", file=query_log)
|
||||
|
||||
|
||||
class QueryLogHandler(DomainHandler):
|
||||
"""
|
||||
Log all received DNS queries to a text file. Use the zone file for
|
||||
preparing responses.
|
||||
"""
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[ResponseAction, None]:
|
||||
log_query(qctx)
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
class EntRcodeChanger(DomainHandler):
|
||||
"""
|
||||
Log all received DNS queries to a text file. Use the zone file for
|
||||
preparing responses, but override the RCODE returned for empty
|
||||
non-terminals (ENTs) to the value specified by the child class. This
|
||||
emulates broken authoritative servers.
|
||||
"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def rcode(self) -> dns_rcode:
|
||||
raise NotImplementedError
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[ResponseAction, None]:
|
||||
assert qctx.zone
|
||||
|
||||
log_query(qctx)
|
||||
|
||||
if (
|
||||
qctx.response.rcode() == dns.rcode.NOERROR
|
||||
and not qctx.response.answer
|
||||
and qctx.response.authority
|
||||
and qctx.response.authority[0].rdtype == dns.rdatatype.SOA
|
||||
and not qctx.zone.get_node(qctx.qname)
|
||||
):
|
||||
qctx.response.set_rcode(self.rcode)
|
||||
yield DnsResponseSend(qctx.response)
|
||||
|
||||
|
||||
class DelayedResponseHandler(DomainHandler):
|
||||
"""
|
||||
Log all received DNS queries to a text file. Use the zone file for
|
||||
preparing responses, but delay sending every answer by the amount of time
|
||||
specified (in seconds) by the child class. This emulates network delays.
|
||||
"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def delay(self) -> float:
|
||||
raise NotImplementedError
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[ResponseAction, None]:
|
||||
log_query(qctx)
|
||||
yield DnsResponseSend(qctx.response, delay=self.delay)
|
@ -324,7 +324,7 @@ sub construct_ans_command {
|
||||
}
|
||||
|
||||
if (-e "$testdir/$server/ans.py") {
|
||||
$ENV{'PYTHONPATH'} = $testdir . ":" . $ENV{'srcdir'};
|
||||
$ENV{'PYTHONPATH'} = $testdir . ":" . $builddir;
|
||||
$command = "$PYTHON -u ans.py 10.53.0.$n $queryport";
|
||||
} elsif (-e "$testdir/$server/ans.pl") {
|
||||
$command = "$PERL ans.pl";
|
||||
|
Loading…
x
Reference in New Issue
Block a user