Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ff0495a6b | ||
|
|
a2042e603e | ||
|
|
5eab352478 | ||
|
|
7f8226a039 | ||
|
|
192627db10 | ||
|
|
dfd37918d6 | ||
|
|
f413ddbe5f | ||
|
|
a799dd04ad | ||
|
|
7faa34c6ee | ||
|
|
575a2e5f11 | ||
|
|
0d9f58b745 | ||
|
|
05d6542e6d | ||
|
|
ff73d37f69 | ||
|
|
9d9e9d9cb1 | ||
|
|
1ab889ee21 | ||
|
|
ae2fd7ef15 | ||
|
|
4e75a20b6a | ||
|
|
807ef8545d |
+6
-6
@@ -383,6 +383,9 @@ stages:
|
||||
SHOTGUN_ROUNDS: 3
|
||||
- &shotgun_rule_other
|
||||
if: '$CI_PIPELINE_SOURCE =~ /^(api|pipeline|schedule|trigger|web)$/'
|
||||
# when using data from a single run, the overall instability of the results
|
||||
# causes quite high false positive rate, rerun the test to attemp to reduce those
|
||||
retry: 1
|
||||
script:
|
||||
- if [ -z "$BASELINE" ]; then export BASELINE=$BIND_BASELINE_VERSION; fi # this dotenv variable can't be set in the rules section, because rules are evaluated before any jobs run
|
||||
- PIPELINE_ID=$(curl -s -X POST --fail
|
||||
@@ -584,7 +587,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
|
||||
@@ -1688,9 +1691,6 @@ respdiff-third-party:
|
||||
|
||||
# Performance tests
|
||||
|
||||
# Run shotgun:udp right away, but delay other shotgun jobs sligthly in order to
|
||||
# allow re-use of the built container image. Otherwise, the jobs would do the
|
||||
# same builds in parallel rather than re-use the already built image.
|
||||
shotgun:udp:
|
||||
<<: *shotgun_job
|
||||
variables:
|
||||
@@ -1701,7 +1701,7 @@ shotgun:tcp:
|
||||
<<: *shotgun_job
|
||||
variables:
|
||||
SHOTGUN_SCENARIO: tcp
|
||||
SHOTGUN_TRAFFIC_MULTIPLIER: 13
|
||||
SHOTGUN_TRAFFIC_MULTIPLIER: 12
|
||||
|
||||
shotgun:dot:
|
||||
<<: *shotgun_job
|
||||
@@ -1722,7 +1722,7 @@ shotgun:doh-get:
|
||||
variables:
|
||||
SHOTGUN_SCENARIO: doh-get
|
||||
SHOTGUN_TRAFFIC_MULTIPLIER: 3
|
||||
SHOTGUN_EVAL_THRESHOLD_LATENCY_PCTL_MAX: 0.3 # bump from the default due to increased tail-end jitter
|
||||
SHOTGUN_EVAL_THRESHOLD_LATENCY_PCTL_MAX: 0.4 # bump from the default due to increased tail-end jitter
|
||||
rules: *shotgun_rules_manual_mr
|
||||
|
||||
.stress-test: &stress_test
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ options {\n\
|
||||
geoip-directory \".\";\n"
|
||||
#endif /* if defined(HAVE_GEOIP2) */
|
||||
"\
|
||||
interface-interval 60;\n\
|
||||
interface-interval 60m;\n\
|
||||
listen-on {any;};\n\
|
||||
listen-on-v6 {any;};\n\
|
||||
match-mapped-addresses no;\n\
|
||||
|
||||
+7
-1
@@ -129,6 +129,7 @@ static int maxudp = 0;
|
||||
/*
|
||||
* -T options:
|
||||
*/
|
||||
static bool cookiealwaysvalid = false;
|
||||
static bool dropedns = false;
|
||||
static bool ednsformerr = false;
|
||||
static bool ednsnotimp = false;
|
||||
@@ -652,7 +653,9 @@ parse_T_opt(char *option) {
|
||||
* force the server to behave (or misbehave) in
|
||||
* specified ways for testing purposes.
|
||||
*/
|
||||
if (!strcmp(option, "dropedns")) {
|
||||
if (!strcmp(option, "cookiealwaysvalid")) {
|
||||
cookiealwaysvalid = true;
|
||||
} else if (!strcmp(option, "dropedns")) {
|
||||
dropedns = true;
|
||||
} else if (!strcmp(option, "ednsformerr")) {
|
||||
ednsformerr = true;
|
||||
@@ -1220,6 +1223,9 @@ setup(void) {
|
||||
/*
|
||||
* Modify server context according to command line options
|
||||
*/
|
||||
if (cookiealwaysvalid) {
|
||||
ns_server_setoption(sctx, NS_SERVER_COOKIEALWAYSVALID, true);
|
||||
}
|
||||
if (disable4) {
|
||||
ns_server_setoption(sctx, NS_SERVER_DISABLE4, true);
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -361,6 +361,23 @@ grep "status: NOERROR," dig.out.test$n >/dev/null || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "Restart NS4 with -T cookiealwaysvalid ($n)"
|
||||
stop_server ns4
|
||||
touch ns4/named.cookiealwaysvalid
|
||||
start_server --noclean --restart --port ${PORT} ns4 || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "test NS6 cookie on NS4 with -T cookiealwaysvalid (expect success) ($n)"
|
||||
ret=0
|
||||
$DIG $DIGOPTS +cookie=$ns6cookie -b 10.53.0.4 +nobadcookie soa . @10.53.0.4 >dig.out.test$n || ret=1
|
||||
grep "; COOKIE:.*(good)" dig.out.test$n >/dev/null || ret=1
|
||||
grep "status: NOERROR," dig.out.test$n >/dev/null || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "check that test server is correctly configured ($n)"
|
||||
ret=0
|
||||
|
||||
@@ -19,6 +19,7 @@ pytestmark = pytest.mark.extra_artifacts(
|
||||
"ans*/ans.run",
|
||||
"ans*/query.log",
|
||||
"ns1/named_dump.db*",
|
||||
"ns4/named.cookiealwaysvalid",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -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.
|
||||
Executable → Regular
+91
-436
@@ -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
@@ -0,0 +1 @@
|
||||
good.db
|
||||
+1
@@ -0,0 +1 @@
|
||||
good.db
|
||||
@@ -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
@@ -0,0 +1 @@
|
||||
good.db
|
||||
@@ -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
@@ -0,0 +1 @@
|
||||
good.db
|
||||
@@ -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.
|
||||
@@ -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
|
||||
Executable → Regular
+34
-273
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
Executable → Regular
+79
-331
@@ -1,345 +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.
|
||||
# This is only returned if a query for b.stale/NS has been made
|
||||
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.4")
|
||||
)
|
||||
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.4")
|
||||
)
|
||||
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()
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -264,7 +264,8 @@ sub construct_ns_command {
|
||||
|
||||
foreach my $t_option(
|
||||
"dropedns", "ednsformerr", "ednsnotimp", "ednsrefused",
|
||||
"noaa", "noedns", "nosoa", "maxudp512", "maxudp1460",
|
||||
"cookiealwaysvalid", "noaa", "noedns", "nosoa",
|
||||
"maxudp512", "maxudp1460",
|
||||
) {
|
||||
if (-e "$testdir/$server/named.$t_option") {
|
||||
$command .= "-T $t_option "
|
||||
@@ -323,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";
|
||||
|
||||
@@ -218,6 +218,7 @@ latex_logo = "isc-logo.pdf"
|
||||
linkcheck_timeout = 10
|
||||
linkcheck_ignore = [
|
||||
"http://127.0.0.1",
|
||||
"https://dl.acm.org",
|
||||
"https://gitlab.isc.org",
|
||||
"https://kb.isc.org",
|
||||
"https://simpleicon.com/",
|
||||
|
||||
@@ -3969,9 +3969,10 @@ Periodic Task Intervals
|
||||
:tags: server
|
||||
:short: Sets the interval at which the server scans the network interface list.
|
||||
|
||||
The server scans the network interface list every :any:`interface-interval`
|
||||
minutes. The default is 60 minutes; the maximum value is 28 days (40320
|
||||
minutes). If set to 0, interface scanning only occurs when the configuration
|
||||
The server scans the network interface list on every interval as specified by
|
||||
:any:`interface-interval`.
|
||||
|
||||
If set to 0, interface scanning only occurs when the configuration
|
||||
file is loaded, or when :any:`automatic-interface-scan` is enabled and supported
|
||||
by the operating system. After the scan, the server begins listening for
|
||||
queries on any newly discovered interfaces (provided they are allowed by the
|
||||
@@ -3979,6 +3980,8 @@ Periodic Task Intervals
|
||||
gone away. For convenience, TTL-style time-unit suffixes may be used to
|
||||
specify the value. It also accepts ISO 8601 duration formats.
|
||||
|
||||
The default is 60 minutes (1 hour); the maximum value is 28 days.
|
||||
|
||||
.. _rrset_ordering:
|
||||
|
||||
RRset Ordering
|
||||
|
||||
+3
-3
@@ -1076,11 +1076,11 @@ new_adbfetch(dns_adb_t *adb) {
|
||||
dns_adbfetch_t *fetch = NULL;
|
||||
|
||||
fetch = isc_mem_get(adb->hmctx, sizeof(*fetch));
|
||||
*fetch = (dns_adbfetch_t){ 0 };
|
||||
*fetch = (dns_adbfetch_t){
|
||||
.magic = DNS_ADBFETCH_MAGIC,
|
||||
};
|
||||
dns_rdataset_init(&fetch->rdataset);
|
||||
|
||||
fetch->magic = DNS_ADBFETCH_MAGIC;
|
||||
|
||||
return fetch;
|
||||
}
|
||||
|
||||
|
||||
+11
-76
@@ -802,10 +802,6 @@ typedef struct respctx {
|
||||
* listening for the correct one */
|
||||
bool truncated; /* response was truncated */
|
||||
bool no_response; /* no response was received */
|
||||
bool glue_in_answer; /* glue may be in the answer
|
||||
* section */
|
||||
bool ns_in_answer; /* NS may be in the answer
|
||||
* section */
|
||||
bool negative; /* is this a negative response? */
|
||||
|
||||
isc_stdtime_t now; /* time info */
|
||||
@@ -7415,15 +7411,6 @@ log_nsid(isc_buffer_t *opt, size_t nsid_len, resquery_t *query, int level,
|
||||
isc_mem_put(mctx, buf, buflen);
|
||||
}
|
||||
|
||||
static bool
|
||||
iscname(dns_message_t *message, dns_name_t *name) {
|
||||
isc_result_t result;
|
||||
|
||||
result = dns_message_findname(message, DNS_SECTION_ANSWER, name,
|
||||
dns_rdatatype_cname, 0, NULL, NULL);
|
||||
return result == ISC_R_SUCCESS ? true : false;
|
||||
}
|
||||
|
||||
static bool
|
||||
betterreferral(respctx_t *rctx) {
|
||||
isc_result_t result;
|
||||
@@ -8345,20 +8332,6 @@ rctx_answer(respctx_t *rctx) {
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
FCTXTRACE3("rctx_answer_positive (AA/fwd)", result);
|
||||
}
|
||||
} else if (iscname(query->rmessage, fctx->name) &&
|
||||
fctx->type != dns_rdatatype_any &&
|
||||
fctx->type != dns_rdatatype_cname)
|
||||
{
|
||||
/*
|
||||
* A BIND8 server could return a non-authoritative
|
||||
* answer when a CNAME is followed. We should treat
|
||||
* it as a valid answer.
|
||||
*/
|
||||
result = rctx_answer_positive(rctx);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
FCTXTRACE3("rctx_answer_positive (!ANY/!CNAME)",
|
||||
result);
|
||||
}
|
||||
} else if (fctx->type != dns_rdatatype_ns && !betterreferral(rctx)) {
|
||||
result = rctx_answer_positive(rctx);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
@@ -8366,41 +8339,12 @@ rctx_answer(respctx_t *rctx) {
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* This may be a delegation. First let's check for
|
||||
* This may be a delegation.
|
||||
*/
|
||||
|
||||
if (fctx->type == dns_rdatatype_ns) {
|
||||
/*
|
||||
* A BIND 8 server could incorrectly return a
|
||||
* non-authoritative answer to an NS query
|
||||
* instead of a referral. Since this answer
|
||||
* lacks the SIGs necessary to do DNSSEC
|
||||
* validation, we must invoke the following
|
||||
* special kludge to treat it as a referral.
|
||||
*/
|
||||
rctx->ns_in_answer = true;
|
||||
result = rctx_answer_none(rctx);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
FCTXTRACE3("rctx_answer_none (NS)", result);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Some other servers may still somehow include
|
||||
* an answer when it should return a referral
|
||||
* with an empty answer. Check to see if we can
|
||||
* treat this as a referral by ignoring the
|
||||
* answer. Further more, there may be an
|
||||
* implementation that moves A/AAAA glue records
|
||||
* to the answer section for that type of
|
||||
* delegation when the query is for that glue
|
||||
* record. glue_in_answer will handle
|
||||
* such a corner case.
|
||||
*/
|
||||
rctx->glue_in_answer = true;
|
||||
result = rctx_answer_none(rctx);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
FCTXTRACE3("rctx_answer_none", result);
|
||||
}
|
||||
result = rctx_answer_none(rctx);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
FCTXTRACE3("rctx_answer_none", result);
|
||||
}
|
||||
|
||||
if (result == DNS_R_DELEGATION) {
|
||||
@@ -9006,14 +8950,12 @@ rctx_answer_none(respctx_t *rctx) {
|
||||
rctx->negative = true;
|
||||
}
|
||||
|
||||
if (!rctx->ns_in_answer && !rctx->glue_in_answer) {
|
||||
/*
|
||||
* Process DNSSEC records in the authority section.
|
||||
*/
|
||||
result = rctx_authority_dnssec(rctx);
|
||||
if (result == ISC_R_COMPLETE) {
|
||||
return rctx->result;
|
||||
}
|
||||
/*
|
||||
* Process DNSSEC records in the authority section.
|
||||
*/
|
||||
result = rctx_authority_dnssec(rctx);
|
||||
if (result == ISC_R_COMPLETE) {
|
||||
return rctx->result;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -9107,12 +9049,7 @@ rctx_authority_negative(respctx_t *rctx) {
|
||||
dns_rdataset_t *rdataset = NULL;
|
||||
bool finished = false;
|
||||
|
||||
if (rctx->ns_in_answer) {
|
||||
INSIST(fctx->type == dns_rdatatype_ns);
|
||||
section = DNS_SECTION_ANSWER;
|
||||
} else {
|
||||
section = DNS_SECTION_AUTHORITY;
|
||||
}
|
||||
section = DNS_SECTION_AUTHORITY;
|
||||
|
||||
result = dns_message_firstname(rctx->query->rmessage, section);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
@@ -9271,8 +9208,6 @@ rctx_authority_dnssec(respctx_t *rctx) {
|
||||
dns_rdataset_t *rdataset = NULL;
|
||||
bool finished = false;
|
||||
|
||||
REQUIRE(!rctx->ns_in_answer && !rctx->glue_in_answer);
|
||||
|
||||
result = dns_message_firstname(rctx->query->rmessage,
|
||||
DNS_SECTION_AUTHORITY);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
|
||||
+13
-1
@@ -1290,6 +1290,7 @@ process_cookie(ns_client_t *client, isc_buffer_t *buf, size_t optlen) {
|
||||
isc_stdtime_t now;
|
||||
uint32_t when;
|
||||
isc_buffer_t db;
|
||||
bool alwaysvalid;
|
||||
|
||||
/*
|
||||
* If we have already seen a cookie option skip this cookie option.
|
||||
@@ -1335,11 +1336,22 @@ process_cookie(ns_client_t *client, isc_buffer_t *buf, size_t optlen) {
|
||||
when = isc_buffer_getuint32(buf);
|
||||
isc_buffer_forward(buf, 8);
|
||||
|
||||
/*
|
||||
* For '-T cookiealwaysvalid' still process everything to not skew any
|
||||
* performance tests involving cookies, but make sure that the cookie
|
||||
* check passes in the end, given the cookie was structurally correct.
|
||||
*/
|
||||
alwaysvalid = ns_server_getoption(client->manager->sctx,
|
||||
NS_SERVER_COOKIEALWAYSVALID);
|
||||
|
||||
/*
|
||||
* Allow for a 5 minute clock skew between servers sharing a secret.
|
||||
* Only accept COOKIE if we have talked to the client in the last hour.
|
||||
*/
|
||||
now = isc_stdtime_now();
|
||||
if (alwaysvalid) {
|
||||
now = when;
|
||||
}
|
||||
if (isc_serial_gt(when, (now + 300)) /* In the future. */ ||
|
||||
isc_serial_lt(when, (now - 3600)) /* In the past. */)
|
||||
{
|
||||
@@ -1352,7 +1364,7 @@ process_cookie(ns_client_t *client, isc_buffer_t *buf, size_t optlen) {
|
||||
isc_buffer_init(&db, dbuf, sizeof(dbuf));
|
||||
compute_cookie(client, when, client->manager->sctx->secret, &db);
|
||||
|
||||
if (isc_safe_memequal(old, dbuf, COOKIE_SIZE)) {
|
||||
if (isc_safe_memequal(old, dbuf, COOKIE_SIZE) || alwaysvalid) {
|
||||
ns_stats_increment(client->manager->sctx->nsstats,
|
||||
ns_statscounter_cookiematch);
|
||||
client->attributes |= NS_CLIENTATTR_HAVECOOKIE;
|
||||
|
||||
+19
-18
@@ -32,24 +32,25 @@
|
||||
|
||||
#include <ns/types.h>
|
||||
|
||||
#define NS_SERVER_LOGQUERIES 0x00000001U /*%< log queries */
|
||||
#define NS_SERVER_NOAA 0x00000002U /*%< -T noaa */
|
||||
#define NS_SERVER_NOSOA 0x00000004U /*%< -T nosoa */
|
||||
#define NS_SERVER_NONEAREST 0x00000008U /*%< -T nonearest */
|
||||
#define NS_SERVER_NOEDNS 0x00000020U /*%< -T noedns */
|
||||
#define NS_SERVER_DROPEDNS 0x00000040U /*%< -T dropedns */
|
||||
#define NS_SERVER_NOTCP 0x00000080U /*%< -T notcp */
|
||||
#define NS_SERVER_DISABLE4 0x00000100U /*%< -6 */
|
||||
#define NS_SERVER_DISABLE6 0x00000200U /*%< -4 */
|
||||
#define NS_SERVER_FIXEDLOCAL 0x00000400U /*%< -T fixedlocal */
|
||||
#define NS_SERVER_SIGVALINSECS 0x00000800U /*%< -T sigvalinsecs */
|
||||
#define NS_SERVER_EDNSFORMERR 0x00001000U /*%< -T ednsformerr (STD13) */
|
||||
#define NS_SERVER_EDNSNOTIMP 0x00002000U /*%< -T ednsnotimp */
|
||||
#define NS_SERVER_EDNSREFUSED 0x00004000U /*%< -T ednsrefused */
|
||||
#define NS_SERVER_TRANSFERINSECS 0x00008000U /*%< -T transferinsecs */
|
||||
#define NS_SERVER_TRANSFERSLOWLY 0x00010000U /*%< -T transferslowly */
|
||||
#define NS_SERVER_TRANSFERSTUCK 0x00020000U /*%< -T transferstuck */
|
||||
#define NS_SERVER_LOGRESPONSES 0x00040000U /*%< log responses */
|
||||
#define NS_SERVER_LOGQUERIES 0x00000001U /*%< log queries */
|
||||
#define NS_SERVER_NOAA 0x00000002U /*%< -T noaa */
|
||||
#define NS_SERVER_NOSOA 0x00000004U /*%< -T nosoa */
|
||||
#define NS_SERVER_NONEAREST 0x00000008U /*%< -T nonearest */
|
||||
#define NS_SERVER_NOEDNS 0x00000020U /*%< -T noedns */
|
||||
#define NS_SERVER_DROPEDNS 0x00000040U /*%< -T dropedns */
|
||||
#define NS_SERVER_NOTCP 0x00000080U /*%< -T notcp */
|
||||
#define NS_SERVER_DISABLE4 0x00000100U /*%< -6 */
|
||||
#define NS_SERVER_DISABLE6 0x00000200U /*%< -4 */
|
||||
#define NS_SERVER_FIXEDLOCAL 0x00000400U /*%< -T fixedlocal */
|
||||
#define NS_SERVER_SIGVALINSECS 0x00000800U /*%< -T sigvalinsecs */
|
||||
#define NS_SERVER_EDNSFORMERR 0x00001000U /*%< -T ednsformerr (STD13) */
|
||||
#define NS_SERVER_EDNSNOTIMP 0x00002000U /*%< -T ednsnotimp */
|
||||
#define NS_SERVER_EDNSREFUSED 0x00004000U /*%< -T ednsrefused */
|
||||
#define NS_SERVER_TRANSFERINSECS 0x00008000U /*%< -T transferinsecs */
|
||||
#define NS_SERVER_TRANSFERSLOWLY 0x00010000U /*%< -T transferslowly */
|
||||
#define NS_SERVER_TRANSFERSTUCK 0x00020000U /*%< -T transferstuck */
|
||||
#define NS_SERVER_LOGRESPONSES 0x00040000U /*%< log responses */
|
||||
#define NS_SERVER_COOKIEALWAYSVALID 0x00080000U /*%< -T cookiealwaysvalid */
|
||||
|
||||
/*%
|
||||
* Type for callback function to get hostname.
|
||||
|
||||
Reference in New Issue
Block a user