Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c7397b9cb | ||
|
|
a5ae37ad0b | ||
|
|
e081ab6ecf | ||
|
|
9604b13d00 | ||
|
|
d7a667d91b | ||
|
|
70470169a9 | ||
|
|
9bb834750d | ||
|
|
d17eda82e0 | ||
|
|
ca3e61a462 | ||
|
|
7f07c989b5 | ||
|
|
83115b931d | ||
|
|
184c7caff9 | ||
|
|
4199d2d5e4 | ||
|
|
6a5e60231f | ||
|
|
49ecb158d4 | ||
|
|
8e7229f641 | ||
|
|
3415392d01 | ||
|
|
2e6107008d | ||
|
|
341b962665 | ||
|
|
d3db9ccf53 | ||
|
|
e1e10adc3a | ||
|
|
d2214cb704 | ||
|
|
3e836a87e6 | ||
|
|
b93cb2e80e | ||
|
|
6c6b8796d3 | ||
|
|
8c9d2eb2bf | ||
|
|
63edc4435f | ||
|
|
ef671919d5 | ||
|
|
329a332708 | ||
|
|
bfbaacc9a0 | ||
|
|
8dba96d71e | ||
|
|
1d688e89b8 | ||
|
|
c2e60f9a5a | ||
|
|
ed727ee924 | ||
|
|
00584d6f29 | ||
|
|
d30b9eb46e | ||
|
|
0c7fa8d572 | ||
|
|
830e548111 | ||
|
|
12e7dfa397 | ||
|
|
c6e5710846 | ||
|
|
575a874582 | ||
|
|
68fe9a5df5 | ||
|
|
8c3f673f37 | ||
|
|
748ed4259b | ||
|
|
a956947fba | ||
|
|
e4c3186a7c | ||
|
|
5764a9d660 | ||
|
|
a2042e603e | ||
|
|
5eab352478 | ||
|
|
7f8226a039 | ||
|
|
192627db10 | ||
|
|
dfd37918d6 | ||
|
|
f413ddbe5f | ||
|
|
a799dd04ad | ||
|
|
7faa34c6ee | ||
|
|
575a2e5f11 | ||
|
|
0d9f58b745 | ||
|
|
05d6542e6d | ||
|
|
ff73d37f69 | ||
|
|
9d9e9d9cb1 | ||
|
|
1ab889ee21 | ||
|
|
ae2fd7ef15 | ||
|
|
4e75a20b6a | ||
|
|
807ef8545d | ||
|
|
06427720f7 | ||
|
|
d0a59277fb | ||
|
|
a8dd267bd0 | ||
|
|
606d30796e | ||
|
|
37ff0aa9c0 | ||
|
|
1c51d44d82 | ||
|
|
3b0b658a52 | ||
|
|
ecde0ea2d7 | ||
|
|
7d98aba3ac | ||
|
|
24eaff7adc | ||
|
|
025ef4d7b8 | ||
|
|
9cfe9f5eb7 | ||
|
|
e6c07b3386 | ||
|
|
1a58bd2113 | ||
|
|
42799ae81f | ||
|
|
de519cd1c9 | ||
|
|
496f7963cd | ||
|
|
98fc14dc75 | ||
|
|
3397212df3 | ||
|
|
947ca25663 | ||
|
|
6c271f6328 | ||
|
|
6ac4cfb948 | ||
|
|
f6f9645ed1 | ||
|
|
1f674ef42e | ||
|
|
f50753f303 | ||
|
|
45ee3715e1 | ||
|
|
7f613c207f | ||
|
|
24ffbdcfea | ||
|
|
e66dc07c68 | ||
|
|
e763d6637f | ||
|
|
334ea1269f | ||
|
|
3309863c97 | ||
|
|
8b3d2e5633 | ||
|
|
3973c2e8c3 | ||
|
|
ecef45bf18 | ||
|
|
33a0cc9823 | ||
|
|
bd711bb839 | ||
|
|
21ca763bca | ||
|
|
474b7a04f1 | ||
|
|
5dfcedd52d | ||
|
|
7c308c2298 | ||
|
|
f0b5f0cbce |
+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
|
||||
|
||||
+33
-5
@@ -289,6 +289,7 @@ help(void) {
|
||||
" form of answers - global "
|
||||
"option)\n"
|
||||
" +[no]showbadcookie (Show BADCOOKIE message)\n"
|
||||
" +[no]showbadvers (Show BADVERS message)\n"
|
||||
" +[no]showsearch (Search with intermediate "
|
||||
"results)\n"
|
||||
" +[no]split=## (Split hex/base64 fields "
|
||||
@@ -327,6 +328,7 @@ help(void) {
|
||||
" +[no]yaml (Present the results as "
|
||||
"YAML)\n"
|
||||
" +[no]zflag (Set Z flag in query)\n"
|
||||
" +[no]zoneversion (Request zone version)\n"
|
||||
" global d-opts and servers (before host name) affect "
|
||||
"all "
|
||||
"queries.\n"
|
||||
@@ -1772,6 +1774,8 @@ plus_option(char *option, bool is_batchfile, bool *need_clone,
|
||||
FULLCHECK("edns");
|
||||
if (!state) {
|
||||
lookup->edns = -1;
|
||||
lookup->original_edns =
|
||||
-1;
|
||||
break;
|
||||
}
|
||||
if (value == NULL) {
|
||||
@@ -1788,6 +1792,7 @@ plus_option(char *option, bool is_batchfile, bool *need_clone,
|
||||
goto exit_or_usage;
|
||||
}
|
||||
lookup->edns = num;
|
||||
lookup->original_edns = num;
|
||||
break;
|
||||
case 'f':
|
||||
FULLCHECK("ednsflags");
|
||||
@@ -2306,8 +2311,18 @@ plus_option(char *option, bool is_batchfile, bool *need_clone,
|
||||
case 'w': /* showsearch */
|
||||
switch (cmd[4]) {
|
||||
case 'b':
|
||||
FULLCHECK("showbadcookie");
|
||||
lookup->showbadcookie = state;
|
||||
switch (cmd[7]) {
|
||||
case 'c':
|
||||
FULLCHECK("showbadcookie");
|
||||
lookup->showbadcookie = state;
|
||||
break;
|
||||
case 'v':
|
||||
FULLCHECK("showbadvers");
|
||||
lookup->showbadvers = state;
|
||||
break;
|
||||
default:
|
||||
goto invalid_option;
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
FULLCHECK("showsearch");
|
||||
@@ -2560,9 +2575,22 @@ plus_option(char *option, bool is_batchfile, bool *need_clone,
|
||||
lookup->rrcomments = -1;
|
||||
}
|
||||
break;
|
||||
case 'z': /* zflag */
|
||||
FULLCHECK("zflag");
|
||||
lookup->zflag = state;
|
||||
case 'z':
|
||||
switch (cmd[1]) {
|
||||
case 'f': /* zflag */
|
||||
FULLCHECK("zflag");
|
||||
lookup->zflag = state;
|
||||
break;
|
||||
case 'o': /* zoneversion */
|
||||
FULLCHECK("zoneversion");
|
||||
if (state && lookup->edns == -1) {
|
||||
lookup->edns = DEFAULT_EDNS_VERSION;
|
||||
}
|
||||
lookup->zoneversion = state;
|
||||
break;
|
||||
default:
|
||||
goto invalid_option;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
invalid_option:
|
||||
|
||||
@@ -614,6 +614,12 @@ abbreviation is unambiguous; for example, :option:`+cd` is equivalent to
|
||||
BADCOOKIE rcode before retrying the request or not. The default
|
||||
is to not show the messages.
|
||||
|
||||
.. option:: +showbadvers, +noshowbadvers
|
||||
|
||||
This option toggles whether to show the message containing the
|
||||
BADVERS rcode before retrying the request or not. The default
|
||||
is to not show the messages.
|
||||
|
||||
.. option:: +showsearch, +noshowsearch
|
||||
|
||||
This option performs [or does not perform] a search showing intermediate results.
|
||||
@@ -751,6 +757,10 @@ abbreviation is unambiguous; for example, :option:`+cd` is equivalent to
|
||||
This option sets [or does not set] the last unassigned DNS header flag in a DNS query.
|
||||
This flag is off by default.
|
||||
|
||||
.. option:: +zoneversion, +nozoneversion
|
||||
|
||||
When enabled, this option includes an EDNS Zone Version request when sending a query.
|
||||
|
||||
Multiple Queries
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
+20
-1
@@ -605,6 +605,7 @@ make_empty_lookup(void) {
|
||||
.idnout = idnout,
|
||||
.udpsize = -1,
|
||||
.edns = -1,
|
||||
.original_edns = -1,
|
||||
.recurse = true,
|
||||
.retries = tries,
|
||||
.comments = true,
|
||||
@@ -704,6 +705,7 @@ clone_lookup(dig_lookup_t *lookold, bool servers) {
|
||||
looknew->opcode = lookold->opcode;
|
||||
looknew->expire = lookold->expire;
|
||||
looknew->nsid = lookold->nsid;
|
||||
looknew->zoneversion = lookold->zoneversion;
|
||||
looknew->tcp_keepalive = lookold->tcp_keepalive;
|
||||
looknew->header_only = lookold->header_only;
|
||||
looknew->https_mode = lookold->https_mode;
|
||||
@@ -738,6 +740,7 @@ clone_lookup(dig_lookup_t *lookold, bool servers) {
|
||||
}
|
||||
|
||||
looknew->showbadcookie = lookold->showbadcookie;
|
||||
looknew->showbadvers = lookold->showbadvers;
|
||||
looknew->sendcookie = lookold->sendcookie;
|
||||
looknew->seenbadcookie = lookold->seenbadcookie;
|
||||
looknew->badcookie = lookold->badcookie;
|
||||
@@ -764,6 +767,7 @@ clone_lookup(dig_lookup_t *lookold, bool servers) {
|
||||
looknew->idnout = lookold->idnout;
|
||||
looknew->udpsize = lookold->udpsize;
|
||||
looknew->edns = lookold->edns;
|
||||
looknew->original_edns = lookold->original_edns;
|
||||
looknew->recurse = lookold->recurse;
|
||||
looknew->aaonly = lookold->aaonly;
|
||||
looknew->adflag = lookold->adflag;
|
||||
@@ -1938,6 +1942,7 @@ followup_lookup(dns_message_t *msg, dig_query_t *query, dns_section_t section) {
|
||||
}
|
||||
domain = dns_fixedname_name(&lookup->fdomain);
|
||||
dns_name_copy(name, domain);
|
||||
lookup->edns = lookup->original_edns;
|
||||
}
|
||||
debug("adding server %s", namestr);
|
||||
num = getaddresses(lookup, namestr, &lresult);
|
||||
@@ -2456,7 +2461,8 @@ setup_lookup(dig_lookup_t *lookup) {
|
||||
lookup->udpsize = DEFAULT_EDNS_BUFSIZE;
|
||||
}
|
||||
if (lookup->edns < 0) {
|
||||
lookup->edns = DEFAULT_EDNS_VERSION;
|
||||
lookup->original_edns = lookup->edns =
|
||||
DEFAULT_EDNS_VERSION;
|
||||
}
|
||||
|
||||
if (lookup->nsid) {
|
||||
@@ -2586,6 +2592,14 @@ setup_lookup(dig_lookup_t *lookup) {
|
||||
i++;
|
||||
}
|
||||
|
||||
if (lookup->zoneversion) {
|
||||
INSIST(i < MAXOPTS);
|
||||
opts[i].code = DNS_OPT_ZONEVERSION;
|
||||
opts[i].length = 0;
|
||||
opts[i].value = NULL;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (lookup->ednsoptscnt != 0) {
|
||||
INSIST(i + lookup->ednsoptscnt <= MAXOPTS);
|
||||
memmove(&opts[i], lookup->ednsopts,
|
||||
@@ -4300,6 +4314,11 @@ recv_done(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
|
||||
if (msg->rcode == dns_rcode_badvers && msg->opt != NULL &&
|
||||
(newedns = ednsvers(msg->opt)) < l->edns && l->ednsneg)
|
||||
{
|
||||
if (l->showbadvers) {
|
||||
dighost_printmessage(query, &b, msg, true);
|
||||
dighost_received(isc_buffer_usedlength(&b), &peer,
|
||||
query);
|
||||
}
|
||||
/*
|
||||
* Add minimum EDNS version required checks here if needed.
|
||||
*/
|
||||
|
||||
+5
-4
@@ -117,11 +117,11 @@ struct dig_lookup {
|
||||
section_answer, section_authority, section_question,
|
||||
seenbadcookie, sendcookie, servfail_stops,
|
||||
setqid, /*% use a speciied query ID */
|
||||
showbadcookie, stats, tcflag, tcp_keepalive, tcp_mode,
|
||||
tcp_mode_set, tls_mode, /*% connect using TLS */
|
||||
trace, /*% dig +trace */
|
||||
showbadcookie, showbadvers, stats, tcflag, tcp_keepalive,
|
||||
tcp_mode, tcp_mode_set, tls_mode, /*% connect using TLS */
|
||||
trace, /*% dig +trace */
|
||||
trace_root, /*% initial query for either +trace or +nssearch */
|
||||
ttlunits, use_usec, waiting_connect, zflag;
|
||||
ttlunits, use_usec, waiting_connect, zflag, zoneversion;
|
||||
char textname[MXNAME]; /*% Name we're going to be looking up */
|
||||
char cmdline[MXNAME];
|
||||
dns_rdatatype_t rdtype;
|
||||
@@ -148,6 +148,7 @@ struct dig_lookup {
|
||||
int nsfound;
|
||||
int16_t udpsize;
|
||||
int16_t edns;
|
||||
int16_t original_edns;
|
||||
int16_t padding;
|
||||
uint32_t ixfr_serial;
|
||||
isc_buffer_t rdatabuf;
|
||||
|
||||
+1
-2
@@ -246,8 +246,7 @@ printsection(dns_message_t *msg, dns_section_t sectionid,
|
||||
(list_type == dns_rdatatype_any ||
|
||||
rdataset->type == list_type)) ||
|
||||
(list_addresses &&
|
||||
(rdataset->type == dns_rdatatype_a ||
|
||||
rdataset->type == dns_rdatatype_aaaa ||
|
||||
(dns_rdatatype_isaddr(rdataset->type) ||
|
||||
rdataset->type == dns_rdatatype_ns ||
|
||||
rdataset->type == dns_rdatatype_ptr))))
|
||||
{
|
||||
|
||||
@@ -99,6 +99,7 @@ options {\n\
|
||||
recursing-file \"named.recursing\";\n\
|
||||
recursive-clients 1000;\n\
|
||||
request-nsid false;\n\
|
||||
request-zoneversion false;\n\
|
||||
resolver-query-timeout 10;\n\
|
||||
# responselog <boolean>;\n\
|
||||
rrset-order { order random; };\n\
|
||||
@@ -239,6 +240,7 @@ options {\n\
|
||||
notify yes;\n\
|
||||
notify-delay 5;\n\
|
||||
notify-to-soa no;\n\
|
||||
provide-zoneversion yes;\n\
|
||||
send-report-channel .;\n\
|
||||
serial-update-method increment;\n\
|
||||
sig-signing-nodes 100;\n\
|
||||
@@ -260,6 +262,7 @@ view \"_bind\" chaos {\n\
|
||||
notify no;\n\
|
||||
allow-new-zones no;\n\
|
||||
max-cache-size 2M;\n\
|
||||
provide-zoneversion no;\n\
|
||||
\n\
|
||||
# Prevent use of this zone in DNS amplified reflection DoS attacks\n\
|
||||
rate-limit {\n\
|
||||
|
||||
@@ -736,7 +736,6 @@ controlkeylist_fromcfg(const cfg_obj_t *keylist, isc_mem_t *mctx,
|
||||
key->secret.length = 0;
|
||||
ISC_LINK_INIT(key, link);
|
||||
ISC_LIST_APPEND(*keyids, key, link);
|
||||
newstr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+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);
|
||||
}
|
||||
|
||||
+29
-5
@@ -1391,6 +1391,13 @@ configure_peer(const cfg_obj_t *cpeer, isc_mem_t *mctx, dns_peer_t **peerp) {
|
||||
CHECK(dns_peer_setrequestnsid(peer, cfg_obj_asboolean(obj)));
|
||||
}
|
||||
|
||||
obj = NULL;
|
||||
(void)cfg_map_get(cpeer, "request-zoneversion", &obj);
|
||||
if (obj != NULL) {
|
||||
CHECK(dns_peer_setrequestzoneversion(peer,
|
||||
cfg_obj_asboolean(obj)));
|
||||
}
|
||||
|
||||
obj = NULL;
|
||||
(void)cfg_map_get(cpeer, "send-cookie", &obj);
|
||||
if (obj != NULL) {
|
||||
@@ -3246,6 +3253,7 @@ create_empty_zone(dns_zone_t *pzone, dns_name_t *name, dns_view_t *view,
|
||||
|
||||
dns_zone_setoption(zone, ~DNS_ZONEOPT_NOCHECKNS, false);
|
||||
dns_zone_setoption(zone, DNS_ZONEOPT_NOCHECKNS, true);
|
||||
dns_zone_setoption(zone, DNS_ZONEOPT_ZONEVERSION, false);
|
||||
dns_zone_setcheckdstype(zone, dns_checkdstype_no);
|
||||
dns_zone_setnotifytype(zone, dns_notifytype_no);
|
||||
dns_zone_setautomatic(zone, true);
|
||||
@@ -3762,7 +3770,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
|
||||
uint32_t maxbits;
|
||||
unsigned int resopts = 0;
|
||||
dns_zone_t *zone = NULL;
|
||||
uint32_t max_clients_per_query;
|
||||
uint32_t clients_per_query, max_clients_per_query;
|
||||
bool empty_zones_enable;
|
||||
const cfg_obj_t *disablelist = NULL;
|
||||
isc_stats_t *resstats = NULL;
|
||||
@@ -5132,6 +5140,11 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
|
||||
INSIST(result == ISC_R_SUCCESS);
|
||||
view->requestnsid = cfg_obj_asboolean(obj);
|
||||
|
||||
obj = NULL;
|
||||
result = named_config_get(maps, "request-zoneversion", &obj);
|
||||
INSIST(result == ISC_R_SUCCESS);
|
||||
view->requestzoneversion = cfg_obj_asboolean(obj);
|
||||
|
||||
obj = NULL;
|
||||
result = named_config_get(maps, "send-cookie", &obj);
|
||||
INSIST(result == ISC_R_SUCCESS);
|
||||
@@ -5168,15 +5181,26 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
|
||||
INSIST(result == ISC_R_SUCCESS);
|
||||
view->v6bias = cfg_obj_asuint32(obj) * 1000;
|
||||
|
||||
obj = NULL;
|
||||
result = named_config_get(maps, "clients-per-query", &obj);
|
||||
INSIST(result == ISC_R_SUCCESS);
|
||||
clients_per_query = cfg_obj_asuint32(obj);
|
||||
|
||||
obj = NULL;
|
||||
result = named_config_get(maps, "max-clients-per-query", &obj);
|
||||
INSIST(result == ISC_R_SUCCESS);
|
||||
max_clients_per_query = cfg_obj_asuint32(obj);
|
||||
|
||||
obj = NULL;
|
||||
result = named_config_get(maps, "clients-per-query", &obj);
|
||||
INSIST(result == ISC_R_SUCCESS);
|
||||
dns_resolver_setclientsperquery(view->resolver, cfg_obj_asuint32(obj),
|
||||
if (max_clients_per_query < clients_per_query) {
|
||||
cfg_obj_log(obj, ISC_LOG_WARNING,
|
||||
"configured clients-per-query (%u) exceeds "
|
||||
"max-clients-per-query (%u); automatically "
|
||||
"adjusting max-clients-per-query to (%u)",
|
||||
clients_per_query, max_clients_per_query,
|
||||
clients_per_query);
|
||||
max_clients_per_query = clients_per_query;
|
||||
}
|
||||
dns_resolver_setclientsperquery(view->resolver, clients_per_query,
|
||||
max_clients_per_query);
|
||||
|
||||
/*
|
||||
|
||||
@@ -358,6 +358,8 @@ init_desc(void) {
|
||||
SET_NSSTATDESC(expireopt, "Expire option received", "ExpireOpt");
|
||||
SET_NSSTATDESC(keepaliveopt, "EDNS TCP keepalive option received",
|
||||
"KeepAliveOpt");
|
||||
SET_NSSTATDESC(zoneversionopt, "ZONEVERSION option received",
|
||||
"ZoneVersionOpt");
|
||||
SET_NSSTATDESC(padopt, "EDNS padding option received", "PadOpt");
|
||||
SET_NSSTATDESC(otheropt, "Other EDNS option received", "OtherOpt");
|
||||
SET_NSSTATDESC(cookiein, "COOKIE option received", "CookieIn");
|
||||
@@ -1603,7 +1605,7 @@ xfrin_xmlrender(dns_zone_t *zone, void *arg) {
|
||||
isc_sockaddr_format(addrp, addr_buf, sizeof(addr_buf));
|
||||
TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR addr_buf));
|
||||
} else if (is_presoa) {
|
||||
addr = dns_zone_getsourceaddr(zone);
|
||||
dns_zone_getsourceaddr(zone, &addr);
|
||||
isc_sockaddr_format(&addr, addr_buf, sizeof(addr_buf));
|
||||
TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR addr_buf));
|
||||
} else {
|
||||
@@ -2660,7 +2662,7 @@ xfrin_jsonrender(dns_zone_t *zone, void *arg) {
|
||||
json_object_object_add(xfrinobj, "localaddr",
|
||||
json_object_new_string(addr_buf));
|
||||
} else if (is_presoa) {
|
||||
addr = dns_zone_getsourceaddr(zone);
|
||||
dns_zone_getsourceaddr(zone, &addr);
|
||||
isc_sockaddr_format(&addr, addr_buf, sizeof(addr_buf));
|
||||
json_object_object_add(xfrinobj, "localaddr",
|
||||
json_object_new_string(addr_buf));
|
||||
|
||||
+12
-8
@@ -1227,6 +1227,12 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
|
||||
dns_zone_setkasp(zone, NULL);
|
||||
}
|
||||
|
||||
obj = NULL;
|
||||
result = named_config_get(maps, "provide-zoneversion", &obj);
|
||||
INSIST(result == ISC_R_SUCCESS && obj != NULL);
|
||||
dns_zone_setoption(zone, DNS_ZONEOPT_ZONEVERSION,
|
||||
cfg_obj_asboolean(obj));
|
||||
|
||||
obj = NULL;
|
||||
result = named_config_get(maps, "notify", &obj);
|
||||
INSIST(result == ISC_R_SUCCESS && obj != NULL);
|
||||
@@ -1279,22 +1285,22 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
|
||||
obj = NULL;
|
||||
result = named_config_get(maps, "parental-source", &obj);
|
||||
INSIST(result == ISC_R_SUCCESS && obj != NULL);
|
||||
CHECK(dns_zone_setparentalsrc4(zone, cfg_obj_assockaddr(obj)));
|
||||
dns_zone_setparentalsrc4(zone, cfg_obj_assockaddr(obj));
|
||||
|
||||
obj = NULL;
|
||||
result = named_config_get(maps, "parental-source-v6", &obj);
|
||||
INSIST(result == ISC_R_SUCCESS && obj != NULL);
|
||||
CHECK(dns_zone_setparentalsrc6(zone, cfg_obj_assockaddr(obj)));
|
||||
dns_zone_setparentalsrc6(zone, cfg_obj_assockaddr(obj));
|
||||
|
||||
obj = NULL;
|
||||
result = named_config_get(maps, "notify-source", &obj);
|
||||
INSIST(result == ISC_R_SUCCESS && obj != NULL);
|
||||
CHECK(dns_zone_setnotifysrc4(zone, cfg_obj_assockaddr(obj)));
|
||||
dns_zone_setnotifysrc4(zone, cfg_obj_assockaddr(obj));
|
||||
|
||||
obj = NULL;
|
||||
result = named_config_get(maps, "notify-source-v6", &obj);
|
||||
INSIST(result == ISC_R_SUCCESS && obj != NULL);
|
||||
CHECK(dns_zone_setnotifysrc6(zone, cfg_obj_assockaddr(obj)));
|
||||
dns_zone_setnotifysrc6(zone, cfg_obj_assockaddr(obj));
|
||||
|
||||
obj = NULL;
|
||||
result = named_config_get(maps, "notify-to-soa", &obj);
|
||||
@@ -1938,14 +1944,12 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
|
||||
obj = NULL;
|
||||
result = named_config_get(maps, "transfer-source", &obj);
|
||||
INSIST(result == ISC_R_SUCCESS && obj != NULL);
|
||||
CHECK(dns_zone_setxfrsource4(mayberaw,
|
||||
cfg_obj_assockaddr(obj)));
|
||||
dns_zone_setxfrsource4(mayberaw, cfg_obj_assockaddr(obj));
|
||||
|
||||
obj = NULL;
|
||||
result = named_config_get(maps, "transfer-source-v6", &obj);
|
||||
INSIST(result == ISC_R_SUCCESS && obj != NULL);
|
||||
CHECK(dns_zone_setxfrsource6(mayberaw,
|
||||
cfg_obj_assockaddr(obj)));
|
||||
dns_zone_setxfrsource6(mayberaw, cfg_obj_assockaddr(obj));
|
||||
|
||||
obj = NULL;
|
||||
(void)named_config_get(maps, "try-tcp-refresh", &obj);
|
||||
|
||||
@@ -28,6 +28,7 @@ options {
|
||||
} except-from {
|
||||
"example";
|
||||
};
|
||||
qname-minimization disabled; // Regression test for GL #4652
|
||||
};
|
||||
|
||||
trust-anchors { };
|
||||
|
||||
@@ -31,6 +31,7 @@ server 0.0.0.0 {
|
||||
request-ixfr no;
|
||||
request-ixfr-max-diffs 0;
|
||||
request-nsid no;
|
||||
request-zoneversion no;
|
||||
require-cookie no;
|
||||
send-cookie no;
|
||||
tcp-keepalive no;
|
||||
@@ -55,6 +56,7 @@ server :: {
|
||||
request-ixfr no;
|
||||
request-ixfr-max-diffs 0;
|
||||
request-nsid no;
|
||||
request-zoneversion no;
|
||||
require-cookie no;
|
||||
send-cookie no;
|
||||
tcp-keepalive no;
|
||||
|
||||
@@ -131,11 +131,13 @@ status=$((status + ret))
|
||||
echo_i "checking that log-report-channel zones fail if '*._er/TXT' is missing ($n)"
|
||||
ret=0
|
||||
$CHECKZONE -R fail example zones/er.db >test.out2.$n 2>&1 || ret=1
|
||||
grep -F "no '*._er/TXT' wildcard found" test.out4.$n >/dev/null && ret=1
|
||||
grep -F "no '*._er/TXT' wildcard found" test.out2.$n >/dev/null && ret=1
|
||||
$CHECKZONE example zones/er-missing.db >test.out3.$n 2>&1 || ret=1
|
||||
grep -F "no '*._er/TXT' wildcard found" test.out4.$n >/dev/null && ret=1
|
||||
grep -F "no '*._er/TXT' wildcard found" test.out3.$n >/dev/null && ret=1
|
||||
$CHECKZONE -R fail example zones/er-missing.db >test.out4.$n 2>&1 && ret=1
|
||||
grep -F "no '*._er/TXT' wildcard found" test.out4.$n >/dev/null || ret=1
|
||||
n=$((n + 1))
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
echo_i "checking that raw zone with bad class is handled ($n)"
|
||||
|
||||
@@ -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
|
||||
@@ -552,16 +569,21 @@ sys.exit(1)'; then
|
||||
$DIG $DIGOPTS @10.53.0.1 tsig. >dig.out.test$n.1 || ret=1
|
||||
grep "status: NOERROR" dig.out.test$n.1 >/dev/null || ret=1
|
||||
rndc_dumpdb ns1
|
||||
# prime cache with NS response for QNAME minimisation
|
||||
grep "$pat" ns1/named_dump.db.test$n >/dev/null || ret=1
|
||||
$DIG $DIGOPTS @10.53.0.1 NS nocookie.tsig >dig.out.test$n.2 || ret=1
|
||||
grep "status: NOERROR" dig.out.test$n.2 >/dev/null || ret=1
|
||||
# check the disabled server response
|
||||
nextpart ns1/named.run >/dev/null
|
||||
$DIG $DIGOPTS @10.53.0.1 nocookie.tsig >dig.out.test$n.2 || ret=1
|
||||
grep "status: NOERROR" dig.out.test$n.2 >/dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.2 >/dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.2 >/dev/null || ret=1
|
||||
$DIG $DIGOPTS @10.53.0.1 nocookie.tsig >dig.out.test$n.3 || ret=1
|
||||
grep "status: NOERROR" dig.out.test$n.3 >/dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.9' dig.out.test$n.3 >/dev/null || ret=1
|
||||
grep 'A.10\.53\.0\.10' dig.out.test$n.3 >/dev/null || ret=1
|
||||
nextpart ns1/named.run >named.run.test$n
|
||||
count=$(grep -c ') [0-9][0-9]* NOERROR 0' named.run.test$n)
|
||||
test $count -eq 2 || ret=1
|
||||
count=$(grep -c '^; COOKIE: ................................' named.run.test$n)
|
||||
test $count -eq 1 || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
fi
|
||||
|
||||
@@ -19,6 +19,7 @@ pytestmark = pytest.mark.extra_artifacts(
|
||||
"ans*/ans.run",
|
||||
"ans*/query.log",
|
||||
"ns1/named_dump.db*",
|
||||
"ns4/named.cookiealwaysvalid",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -36,4 +36,5 @@ zone "example" {
|
||||
zone "example.tld" {
|
||||
type primary;
|
||||
file "example.tld.db";
|
||||
provide-zoneversion no;
|
||||
};
|
||||
|
||||
@@ -801,6 +801,73 @@ if [ -x "$DIG" ]; then
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "checking dig +zoneversion to a authoritative server ($n)"
|
||||
ret=0
|
||||
dig_with_opts @10.53.0.2 +zoneversion a.example >dig.out.test$n 2>&1 || ret=1
|
||||
pat="; ZONEVERSION: ZONE: example, SOA-SERIAL: 2000042407"
|
||||
grep "$pat" dig.out.test$n >/dev/null || ret=1
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "checking dig +zoneversion to a authoritative server with zoneversion disabled ($n)"
|
||||
ret=0
|
||||
dig_with_opts @10.53.0.2 +zoneversion a.example.tld >dig.out.test$n 2>&1 || ret=1
|
||||
grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1
|
||||
grep "; ZONEVERSION:" dig.out.test$n >/dev/null && ret=1
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
if [ $HAS_PYYAML -ne 0 ]; then
|
||||
n=$((n + 1))
|
||||
echo_i "checking dig +yaml +zoneversion to a authoritative server ($n)"
|
||||
ret=0
|
||||
dig_with_opts @10.53.0.2 +yaml +zoneversion a.example >dig.out.test$n 2>&1 || ret=1
|
||||
$PYTHON yamlget.py dig.out.test$n 0 message response_message_data OPT_PSEUDOSECTION EDNS ZONEVERSION >yamlget.out.test$n 2>&1 || ret=1
|
||||
read -r value <yamlget.out.test$n
|
||||
expected="{'ZONE': 'example', 'SOA-SERIAL': 2000042407}"
|
||||
[ "$value" = "$expected" ] || ret=1
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
fi
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "checking dig +ednsopt=ZONEVERSION:<answer> to a authoritative server ($n)"
|
||||
ret=0
|
||||
dig_with_opts @10.53.0.2 +ednsopt=ZONEVERSION:0100000007DA a.example >dig.out.test$n 2>&1 || ret=1
|
||||
grep "status: FORMERR" dig.out.test$n >/dev/null || ret=1
|
||||
grep "; ZONEVERSION:" dig.out.test$n >/dev/null && ret=1
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "checking dig +zoneversion to a recursive server ($n)"
|
||||
ret=0
|
||||
dig_with_opts @10.53.0.3 +zoneversion a.example >dig.out.test$n 2>&1 || ret=1
|
||||
grep '; ZONEVERSION:' dig.out.test$n >/dev/null && ret=1
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "checking display of non serial type zoneversion ($n)"
|
||||
ret=0
|
||||
dig_with_opts @10.53.0.2 +qr +ednsopt=ZONEVERSION:0100000007DA a.example >dig.out.test$n 2>&1 || ret=1
|
||||
grep '; ZONEVERSION: LABELS: 1, TYPE: 0, VALUE: 000007da ("....")' dig.out.test$n >/dev/null && ret=1
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
if [ $HAS_PYYAML -ne 0 ]; then
|
||||
n=$((n + 1))
|
||||
echo_i "checking display of non serial type zoneversion +yaml ($n)"
|
||||
ret=0
|
||||
dig_with_opts @10.53.0.2 +qr +ednsopt=ZONEVERSION:0100000007DA a.example +yaml >dig.out.test$n 2>&1 || ret=1
|
||||
$PYTHON yamlget.py dig.out.test$n 0 message query_message_data OPT_PSEUDOSECTION EDNS ZONEVERSION >yamlget.out.test$n 2>&1 || ret=1
|
||||
expected="{'ZONE': 'example', 'TYPE': 1, 'VALUE': 000007da, PVALUE: '....'}"
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
fi
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "check that dig gracefully handles bad escape in domain name ($n)"
|
||||
ret=0
|
||||
@@ -1135,6 +1202,16 @@ if [ -x "$DIG" ]; then
|
||||
grep "; EDNS: version: 0, flags:; udp: 1232" dig.out.test$n >/dev/null || ret=1
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "check that dig +showbadvers works ($n)"
|
||||
dig_with_opts @10.53.0.3 +edns=1 +qr +showbadvers a.example >dig.out.test$n 2>&1 || ret=1
|
||||
grep "; EDNS: version: 1, flags:; udp: 1232" dig.out.test$n >/dev/null || ret=1
|
||||
grep "; EDNS: version: 0, flags:; udp: 1232" dig.out.test$n >/dev/null || ret=1
|
||||
grep -F "status: BADVERS" dig.out.test$n >/dev/null || ret=1
|
||||
grep -F "status: NOERROR" dig.out.test$n >/dev/null || ret=1
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
else
|
||||
echo_i "$DIG is needed, so skipping these dig tests"
|
||||
fi
|
||||
|
||||
@@ -64,7 +64,7 @@ for subdomain in digest-alg-unsupported ds-unsupported secure badds \
|
||||
kskonly update-nsec3 auto-nsec auto-nsec3 secure.below-cname \
|
||||
ttlpatch split-dnssec split-smart expired expiring upper lower \
|
||||
dnskey-unknown dnskey-unsupported dnskey-unsupported-2 \
|
||||
dnskey-nsec3-unknown managed-future revkey \
|
||||
dnskey-nsec3-unknown managed-future future revkey \
|
||||
dname-at-apex-nsec3 occluded rsasha1 rsasha1-1024; do
|
||||
cp "../ns3/dsset-$subdomain.example." .
|
||||
done
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
; This is a key-signing key, keyid 23640, for .
|
||||
; Created: 20250310185208 (Mon Mar 10 18:52:08 2025)
|
||||
; Publish: 20250310185208 (Mon Mar 10 18:52:08 2025)
|
||||
; Activate: 20250310185208 (Mon Mar 10 18:52:08 2025)
|
||||
; Revoke: 20250310185208 (Mon Mar 10 18:52:08 2025)
|
||||
. IN DNSKEY 257 3 13 uKwpRtMH+9iuUk/Xj6LciIP5ZckaBtXaUqxUxzJYexXjvxGZGX4470Jv hq2NCI3HBZQNaCCP/h9sluhIzRGPTA==
|
||||
@@ -0,0 +1,7 @@
|
||||
Private-key-format: v1.3
|
||||
Algorithm: 13 (ECDSAP256SHA256)
|
||||
PrivateKey: m5udfGNSijISQ8Tfp4kx09O1em4PErLUw/mCj3SKmqw=
|
||||
Created: 20250310185208
|
||||
Publish: 20250310185208
|
||||
Activate: 20250310185208
|
||||
Revoke: 20250310185208
|
||||
@@ -0,0 +1,5 @@
|
||||
; This is a zone-signing key, keyid 23768, for .
|
||||
; Created: 20250310185208 (Mon Mar 10 18:52:08 2025)
|
||||
; Publish: 20250310185208 (Mon Mar 10 18:52:08 2025)
|
||||
; Activate: 20250310185208 (Mon Mar 10 18:52:08 2025)
|
||||
. IN DNSKEY 256 3 13 TFelYtTRBWeA9A307vvuWIcaNwW4txW4RgSELtsi46ZQs24ncRxmxtFf uJuPyVXePNiE4HNI9CIowGUsn5WuBw==
|
||||
@@ -0,0 +1,37 @@
|
||||
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
;
|
||||
; SPDX-License-Identifier: MPL-2.0
|
||||
;
|
||||
; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
;
|
||||
; See the COPYRIGHT file distributed with this work for additional
|
||||
; information regarding copyright ownership.
|
||||
|
||||
; This is a zone which has two DNSKEY records, both of which have
|
||||
; existing private key files available. They should be loaded automatically
|
||||
; and the zone correctly signed.
|
||||
;
|
||||
$TTL 30 ; 30 seconds
|
||||
. IN SOA a.root.servers.nil. each.isc.org. (
|
||||
2000042101 ; serial
|
||||
600 ; refresh (10 minutes)
|
||||
600 ; retry (10 minutes)
|
||||
1200 ; expire (20 minutes)
|
||||
600 ; minimum (10 minutes)
|
||||
)
|
||||
NS a.root-servers.nil.
|
||||
DNSKEY 256 3 13 (
|
||||
TFelYtTRBWeA9A307vvuWIcaNwW4txW4RgSELtsi46ZQ
|
||||
s24ncRxmxtFfuJuPyVXePNiE4HNI9CIowGUsn5WuBw==
|
||||
) ; ZSK; alg = ECDSAP256SHA256 ; key id = 23768
|
||||
DNSKEY 257 3 13 (
|
||||
OSmhpULEDCUzHCBeDU5uJXzkCcGuW2qrkQznKRPGhRZN
|
||||
j7ZUIGInGzM5Um5m02ULWt8tKbi55NJUeifKWegQ0g==
|
||||
) ; KSK; alg = ECDSAP256SHA256 ; key id = 22255
|
||||
DNSKEY 385 3 13 (
|
||||
uKwpRtMH+9iuUk/Xj6LciIP5ZckaBtXaUqxUxzJYexXj
|
||||
vxGZGX4470Jvhq2NCI3HBZQNaCCP/h9sluhIzRGPTA==
|
||||
) ; revoked KSK; alg = ECDSAP256SHA256 ; key id = 23768
|
||||
a.root-servers.nil. A 10.53.0.1
|
||||
@@ -1564,6 +1564,18 @@ n=$((n + 1))
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
echo_ic "revoked KSK ID collides with ZSK ($n)"
|
||||
ret=0
|
||||
# signing should fail, but should not coredump
|
||||
(
|
||||
cd signer/general || exit 0
|
||||
rm -f signed.zone
|
||||
$SIGNER -S -f signed.zone -o . test12.zone >signer.out.$n
|
||||
) && ret=1
|
||||
n=$((n + 1))
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
echo_ic "check that dnssec-signzone rejects excessive NSEC3 iterations ($n)"
|
||||
ret=0
|
||||
(
|
||||
@@ -2179,7 +2191,7 @@ echo_i "checking RRSIG query from cache ($n)"
|
||||
ret=0
|
||||
dig_with_opts normalthenrrsig.secure.example. @10.53.0.4 a >/dev/null || ret=1
|
||||
ans=$(dig_with_opts +short normalthenrrsig.secure.example. @10.53.0.4 rrsig) || ret=1
|
||||
expect=$(dig_with_opts +short normalthenrrsig.secure.example. @10.53.0.3 rrsig | grep '^A') || ret=1
|
||||
expect=$(dig_with_opts +short normalthenrrsig.secure.example. @10.53.0.3 rrsig | grep -E '^(A|NSEC)') || ret=1
|
||||
test "$ans" = "$expect" || ret=1
|
||||
# also check that RA is set
|
||||
dig_with_opts normalthenrrsig.secure.example. @10.53.0.4 rrsig >dig.out.ns4.test$n || ret=1
|
||||
@@ -2859,6 +2871,19 @@ dig_with_opts +noauth expired.example. +dnssec @10.53.0.4 soa >dig.out.ns4.test$
|
||||
grep "SERVFAIL" dig.out.ns4.test$n >/dev/null || ret=1
|
||||
grep "flags:.*ad.*QUERY" dig.out.ns4.test$n >/dev/null && ret=1
|
||||
grep "expired.example/.*: RRSIG has expired" ns4/named.run >/dev/null || ret=1
|
||||
grep "; EDE: 7 (Signature Expired): (expired.example/DNSKEY)" dig.out.ns4.test$n >/dev/null || ret=1
|
||||
n=$((n + 1))
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
status=$((status + ret))
|
||||
echo_i "checking signatures in the future do not validate ($n)"
|
||||
ret=0
|
||||
dig_with_opts +noauth future.example. +dnssec @10.53.0.4 soa >dig.out.ns4.test$n || ret=1
|
||||
grep "SERVFAIL" dig.out.ns4.test$n >/dev/null || ret=1
|
||||
grep "flags:.*ad.*QUERY" dig.out.ns4.test$n >/dev/null && ret=1
|
||||
grep "future.example/.*: RRSIG validity period has not begun" ns4/named.run >/dev/null || ret=1
|
||||
grep "; EDE: 8 (Signature Not Yet Valid): (future.example/DNSKEY)" dig.out.ns4.test$n >/dev/null || ret=1
|
||||
n=$((n + 1))
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
@@ -3755,7 +3780,7 @@ status=$((status + ret))
|
||||
echo_i "checking EDE code 1 for bad alg mnemonic ($n)"
|
||||
ret=0
|
||||
dig_with_opts @10.53.0.4 badalg.secure.example >dig.out.ns4.test$n || ret=1
|
||||
grep "; EDE: 1 (Unsupported DNSKEY Algorithm): (ECDSAP256SHA256 badalg.secure.example/A)" dig.out.ns4.test$n >/dev/null || ret=1
|
||||
grep "; EDE: 1 (Unsupported DNSKEY Algorithm): (ECDSAP256SHA256 badalg.secure.example/NSEC)" dig.out.ns4.test$n >/dev/null || ret=1
|
||||
grep "flags:.*ad.*QUERY" dig.out.ns4.test$n >/dev/null && ret=1
|
||||
n=$((n + 1))
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
|
||||
@@ -232,9 +232,7 @@ addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
|
||||
dns_fixedname_init(&name);
|
||||
CHECK(dns__db_addrdataset(sampledb->db, node, version, now, rdataset,
|
||||
options, addedrdataset DNS__DB_FLARG_PASS));
|
||||
if (rdataset->type == dns_rdatatype_a ||
|
||||
rdataset->type == dns_rdatatype_aaaa)
|
||||
{
|
||||
if (dns_rdatatype_isaddr(rdataset->type)) {
|
||||
CHECK(dns_db_nodefullname(sampledb->db, node,
|
||||
dns_fixedname_name(&name)));
|
||||
CHECK(syncptrs(sampledb->inst, dns_fixedname_name(&name),
|
||||
@@ -263,9 +261,7 @@ subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (rdataset->type == dns_rdatatype_a ||
|
||||
rdataset->type == dns_rdatatype_aaaa)
|
||||
{
|
||||
if (dns_rdatatype_isaddr(rdataset->type)) {
|
||||
CHECK(dns_db_nodefullname(sampledb->db, node,
|
||||
dns_fixedname_name(&name)));
|
||||
CHECK(syncptrs(sampledb->inst, dns_fixedname_name(&name),
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
options {
|
||||
query-source address 10.53.0.5;
|
||||
notify-source 10.53.0.5;
|
||||
transfer-source 10.53.0.5;
|
||||
port @PORT@;
|
||||
directory ".";
|
||||
pid-file "named.pid";
|
||||
listen-on { 10.53.0.5; };
|
||||
listen-on-v6 { none; };
|
||||
recursion yes;
|
||||
dnssec-validation yes;
|
||||
notify yes;
|
||||
stale-answer-enable yes;
|
||||
stale-cache-enable yes;
|
||||
stale-answer-client-timeout 0;
|
||||
/* max-clients-per-query < clients-per-query */
|
||||
clients-per-query 10;
|
||||
max-clients-per-query 5;
|
||||
};
|
||||
|
||||
trust-anchors { };
|
||||
|
||||
server 10.53.0.4 {
|
||||
edns no;
|
||||
};
|
||||
|
||||
key rndc_key {
|
||||
secret "1234abcd8765";
|
||||
algorithm @DEFAULT_HMAC@;
|
||||
};
|
||||
|
||||
controls {
|
||||
inet 10.53.0.5 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
|
||||
};
|
||||
|
||||
zone "." {
|
||||
type hint;
|
||||
file "root.hint";
|
||||
};
|
||||
@@ -328,5 +328,14 @@ echo_i "$zspill clients spilled (expected $expected)"
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "checking a warning is logged if max-clients-per-query < clients-per-query ($n)"
|
||||
ret=0
|
||||
copy_setports ns5/named3.conf.in ns5/named.conf
|
||||
rndc_reconfig ns5 10.53.0.5
|
||||
wait_for_message ns5/named.run "configured clients-per-query (10) exceeds max-clients-per-query (5); automatically adjusting max-clients-per-query to (10)" || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
echo_i "exit status: $status"
|
||||
[ $status -eq 0 ] || exit 1
|
||||
|
||||
@@ -224,6 +224,20 @@ class DnsProtocol(enum.Enum):
|
||||
TCP = enum.auto()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Peer:
|
||||
"""
|
||||
Pretty-printed connection endpoint.
|
||||
"""
|
||||
|
||||
host: str
|
||||
port: int
|
||||
|
||||
def __str__(self) -> str:
|
||||
host = f"[{self.host}]" if ":" in self.host else self.host
|
||||
return f"{host}:{self.port}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class QueryContext:
|
||||
"""
|
||||
@@ -232,7 +246,7 @@ class QueryContext:
|
||||
|
||||
query: dns.message.Message
|
||||
response: dns.message.Message
|
||||
peer: Tuple[str, int]
|
||||
peer: Peer
|
||||
protocol: DnsProtocol
|
||||
zone: Optional[dns.zone.Zone] = None
|
||||
soa: Optional[dns.rrset.RRset] = None
|
||||
@@ -513,56 +527,110 @@ class AsyncDnsServer(AsyncServer):
|
||||
self._zone_tree.add(zone)
|
||||
|
||||
async def _handle_udp(
|
||||
self, wire: bytes, peer: Tuple[str, int], transport: asyncio.DatagramTransport
|
||||
self, wire: bytes, addr: Tuple[str, int], transport: asyncio.DatagramTransport
|
||||
) -> None:
|
||||
logging.debug("Received UDP message: %s", wire.hex())
|
||||
peer = Peer(addr[0], addr[1])
|
||||
responses = self._handle_query(wire, peer, DnsProtocol.UDP)
|
||||
async for response in responses:
|
||||
transport.sendto(response, peer)
|
||||
transport.sendto(response, addr)
|
||||
|
||||
async def _handle_tcp(
|
||||
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
|
||||
) -> None:
|
||||
wire_length_bytes = await reader.read(2)
|
||||
(wire_length,) = struct.unpack("!H", wire_length_bytes)
|
||||
logging.debug("Receiving TCP message (%d octets)...", wire_length)
|
||||
peer_info = writer.get_extra_info("peername")
|
||||
peer = Peer(peer_info[0], peer_info[1])
|
||||
logging.debug("Accepted TCP connection from %s", peer)
|
||||
|
||||
wire = await reader.read(wire_length)
|
||||
full_message = wire_length_bytes + wire
|
||||
logging.debug("Received complete TCP message: %s", full_message.hex())
|
||||
|
||||
peer = writer.get_extra_info("peername")
|
||||
responses = self._handle_query(wire, peer, DnsProtocol.TCP)
|
||||
async for response in responses:
|
||||
writer.write(response)
|
||||
while True:
|
||||
try:
|
||||
await writer.drain()
|
||||
wire = await self._read_tcp_query(reader, peer)
|
||||
if not wire:
|
||||
break
|
||||
await self._send_tcp_response(writer, peer, wire)
|
||||
except ConnectionResetError:
|
||||
logging.error(
|
||||
"TCP connection from %s reset by peer", self._format_peer(peer)
|
||||
)
|
||||
logging.error("TCP connection from %s reset by peer", peer)
|
||||
return
|
||||
|
||||
logging.debug("Closing TCP connection from %s", peer)
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
|
||||
def _format_peer(self, peer: Tuple[str, int]) -> str:
|
||||
host = peer[0]
|
||||
port = peer[1]
|
||||
if "::" in host:
|
||||
host = f"[{host}]"
|
||||
return f"{host}:{port}"
|
||||
async def _read_tcp_query(
|
||||
self, reader: asyncio.StreamReader, peer: Peer
|
||||
) -> Optional[bytes]:
|
||||
wire_length = await self._read_tcp_query_wire_length(reader, peer)
|
||||
if not wire_length:
|
||||
return None
|
||||
|
||||
def _log_query(
|
||||
self, qctx: QueryContext, peer: Tuple[str, int], protocol: DnsProtocol
|
||||
return await self._read_tcp_query_wire(reader, peer, wire_length)
|
||||
|
||||
async def _read_tcp_query_wire_length(
|
||||
self, reader: asyncio.StreamReader, peer: Peer
|
||||
) -> Optional[int]:
|
||||
logging.debug("Receiving TCP message length from %s...", peer)
|
||||
|
||||
wire_length_bytes = await self._read_tcp_octets(reader, peer, 2)
|
||||
if not wire_length_bytes:
|
||||
return None
|
||||
|
||||
(wire_length,) = struct.unpack("!H", wire_length_bytes)
|
||||
|
||||
return wire_length
|
||||
|
||||
async def _read_tcp_query_wire(
|
||||
self, reader: asyncio.StreamReader, peer: Peer, wire_length: int
|
||||
) -> Optional[bytes]:
|
||||
logging.debug("Receiving TCP message (%d octets) from %s...", wire_length, peer)
|
||||
|
||||
wire = await self._read_tcp_octets(reader, peer, wire_length)
|
||||
if not wire:
|
||||
return None
|
||||
|
||||
logging.debug("Received complete TCP message from %s: %s", peer, wire.hex())
|
||||
|
||||
return wire
|
||||
|
||||
async def _read_tcp_octets(
|
||||
self, reader: asyncio.StreamReader, peer: Peer, expected: int
|
||||
) -> Optional[bytes]:
|
||||
buffer = b""
|
||||
|
||||
while len(buffer) < expected:
|
||||
chunk = await reader.read(expected - len(buffer))
|
||||
if not chunk:
|
||||
if buffer:
|
||||
logging.debug(
|
||||
"Received short TCP message (%d octets) from %s: %s",
|
||||
len(buffer),
|
||||
peer,
|
||||
buffer.hex(),
|
||||
)
|
||||
else:
|
||||
logging.debug("Received disconnect from %s", peer)
|
||||
return None
|
||||
|
||||
logging.debug("Received %d TCP octets from %s", len(chunk), peer)
|
||||
buffer += chunk
|
||||
|
||||
return buffer
|
||||
|
||||
async def _send_tcp_response(
|
||||
self, writer: asyncio.StreamWriter, peer: Peer, wire: bytes
|
||||
) -> None:
|
||||
responses = self._handle_query(wire, peer, DnsProtocol.TCP)
|
||||
async for response in responses:
|
||||
writer.write(response)
|
||||
await writer.drain()
|
||||
|
||||
def _log_query(self, qctx: QueryContext, peer: Peer, protocol: DnsProtocol) -> None:
|
||||
logging.info(
|
||||
"Received %s/%s/%s (ID=%d) query from %s (%s)",
|
||||
qctx.qname.to_text(omit_final_dot=True),
|
||||
dns.rdataclass.to_text(qctx.qclass),
|
||||
dns.rdatatype.to_text(qctx.qtype),
|
||||
qctx.query.id,
|
||||
self._format_peer(peer),
|
||||
peer,
|
||||
protocol.name,
|
||||
)
|
||||
logging.debug(
|
||||
@@ -573,14 +641,14 @@ class AsyncDnsServer(AsyncServer):
|
||||
self,
|
||||
qctx: QueryContext,
|
||||
response: Optional[Union[dns.message.Message, bytes]],
|
||||
peer: Tuple[str, int],
|
||||
peer: Peer,
|
||||
protocol: DnsProtocol,
|
||||
) -> None:
|
||||
if not response:
|
||||
logging.info(
|
||||
"Not sending a response to query (ID=%d) from %s (%s)",
|
||||
qctx.query.id,
|
||||
self._format_peer(peer),
|
||||
peer,
|
||||
protocol.name,
|
||||
)
|
||||
return
|
||||
@@ -606,7 +674,7 @@ class AsyncDnsServer(AsyncServer):
|
||||
len(response.authority),
|
||||
len(response.additional),
|
||||
qctx.query.id,
|
||||
self._format_peer(peer),
|
||||
peer,
|
||||
protocol.name,
|
||||
)
|
||||
logging.debug(
|
||||
@@ -618,13 +686,13 @@ class AsyncDnsServer(AsyncServer):
|
||||
"Sending response (%d bytes) to a query (ID=%d) from %s (%s)",
|
||||
len(response),
|
||||
qctx.query.id,
|
||||
self._format_peer(peer),
|
||||
peer,
|
||||
protocol.name,
|
||||
)
|
||||
logging.debug("[OUT] %s", response.hex())
|
||||
|
||||
async def _handle_query(
|
||||
self, wire: bytes, peer: Tuple[str, int], protocol: DnsProtocol
|
||||
self, wire: bytes, peer: Peer, protocol: DnsProtocol
|
||||
) -> AsyncGenerator[bytes, None]:
|
||||
"""
|
||||
Yield wire data to send as a response over the established transport.
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
import difflib
|
||||
import shutil
|
||||
from typing import Optional
|
||||
|
||||
@@ -98,28 +97,6 @@ def zones_equal(
|
||||
assert found_rdataset.ttl == rdataset.ttl
|
||||
|
||||
|
||||
def zone_contains(
|
||||
zone: dns.zone.Zone, rrset: dns.rrset.RRset, compare_ttl=False
|
||||
) -> bool:
|
||||
"""Check if a zone contains RRset"""
|
||||
|
||||
def compare_rrs(rr1, rrset):
|
||||
rr2 = next((other_rr for other_rr in rrset if rr1 == other_rr), None)
|
||||
if rr2 is None:
|
||||
return False
|
||||
if compare_ttl:
|
||||
return rr1.ttl == rr2.ttl
|
||||
return True
|
||||
|
||||
for _, node in zone.nodes.items():
|
||||
for rdataset in node:
|
||||
for rr in rdataset:
|
||||
if compare_rrs(rr, rrset):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def is_executable(cmd: str, errmsg: str) -> None:
|
||||
executable = shutil.which(cmd)
|
||||
assert executable is not None, errmsg
|
||||
@@ -151,32 +128,3 @@ def is_response_to(response: dns.message.Message, query: dns.message.Message) ->
|
||||
single_question(response)
|
||||
single_question(query)
|
||||
assert query.is_response(response), str(response)
|
||||
|
||||
|
||||
def file_contents_contain(file, substr):
|
||||
with open(file, "r", encoding="utf-8") as fp:
|
||||
for line in fp:
|
||||
if f"{substr}" in line:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def file_contents_equal(file1, file2):
|
||||
def normalize_line(line):
|
||||
# remove trailing&leading whitespace and replace multiple whitespaces
|
||||
return " ".join(line.split())
|
||||
|
||||
def read_lines(file_path):
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
return [normalize_line(line) for line in file.readlines()]
|
||||
|
||||
lines1 = read_lines(file1)
|
||||
lines2 = read_lines(file2)
|
||||
|
||||
differ = difflib.Differ()
|
||||
diff = differ.compare(lines1, lines2)
|
||||
|
||||
for line in diff:
|
||||
assert not line.startswith("+ ") and not line.startswith(
|
||||
"- "
|
||||
), f'file contents of "{file1}" and "{file2}" differ'
|
||||
|
||||
@@ -10,34 +10,24 @@
|
||||
# information regarding copyright ownership.
|
||||
|
||||
from functools import total_ordering
|
||||
import glob
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from typing import List, Optional, Union
|
||||
from typing import Optional, Union
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import dns
|
||||
import dns.tsig
|
||||
import isctest.log
|
||||
import isctest.query
|
||||
|
||||
DEFAULT_TTL = 300
|
||||
|
||||
NEXT_KEY_EVENT_THRESHOLD = 100
|
||||
|
||||
|
||||
def _query(server, qname, qtype, tsig=None):
|
||||
def _query(server, qname, qtype):
|
||||
query = dns.message.make_query(qname, qtype, use_edns=True, want_dnssec=True)
|
||||
|
||||
if tsig is not None:
|
||||
tsigkey = tsig.split(":")
|
||||
keyring = dns.tsig.Key(tsigkey[1], tsigkey[2], tsigkey[0])
|
||||
query.use_tsig(keyring)
|
||||
|
||||
try:
|
||||
response = isctest.query.tcp(query, server.ip, server.ports.dns, timeout=3)
|
||||
except dns.exception.Timeout:
|
||||
@@ -47,158 +37,6 @@ def _query(server, qname, qtype, tsig=None):
|
||||
return response
|
||||
|
||||
|
||||
class KeyProperties:
|
||||
"""
|
||||
Represent the (expected) properties a key should have.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, properties: dict, metadata: dict, timing: dict):
|
||||
self.name = name
|
||||
self.key = None
|
||||
self.properties = properties
|
||||
self.metadata = metadata
|
||||
self.timing = timing
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
@staticmethod
|
||||
def default(with_state=True) -> "KeyProperties":
|
||||
result = KeyProperties.__new__(KeyProperties)
|
||||
result.name = "DEFAULT"
|
||||
result.key = None
|
||||
result.timing = {}
|
||||
result.properties = {
|
||||
"expect": True,
|
||||
"private": True,
|
||||
"legacy": False,
|
||||
"role": "csk",
|
||||
"role_full": "key-signing",
|
||||
"dnskey_ttl": 3600,
|
||||
"flags": 257,
|
||||
}
|
||||
result.metadata = {
|
||||
"Algorithm": 13, # ECDSAP256SHA256
|
||||
"Length": 256,
|
||||
"Lifetime": 0,
|
||||
"KSK": "yes",
|
||||
"ZSK": "yes",
|
||||
}
|
||||
if with_state:
|
||||
result.metadata["GoalState"] = "omnipresent"
|
||||
result.metadata["DNSKEYState"] = "rumoured"
|
||||
result.metadata["KRRSIGState"] = "rumoured"
|
||||
result.metadata["ZRRSIGState"] = "rumoured"
|
||||
result.metadata["DSState"] = "hidden"
|
||||
|
||||
return result
|
||||
|
||||
def Ipub(self, config):
|
||||
ipub = timedelta(0)
|
||||
|
||||
if self.key.get_metadata("Predecessor", must_exist=False) != "undefined":
|
||||
# Ipub = Dprp + TTLkey
|
||||
ipub = (
|
||||
config["dnskey-ttl"]
|
||||
+ config["zone-propagation-delay"]
|
||||
+ config["publish-safety"]
|
||||
)
|
||||
|
||||
self.timing["Active"] = self.timing["Published"] + ipub
|
||||
|
||||
def IpubC(self, config):
|
||||
if not self.key.is_ksk():
|
||||
return
|
||||
|
||||
ttl1 = config["dnskey-ttl"] + config["publish-safety"]
|
||||
ttl2 = timedelta(0)
|
||||
|
||||
if self.key.get_metadata("Predecessor", must_exist=False) == "undefined":
|
||||
# If this is the first key, we also need to wait until the zone
|
||||
# signatures are omnipresent. Use max-zone-ttl instead of
|
||||
# dnskey-ttl, and no publish-safety (because we are looking at
|
||||
# signatures here, not the public key).
|
||||
ttl2 = config["max-zone-ttl"]
|
||||
|
||||
# IpubC = DprpC + TTLkey
|
||||
ipubc = config["zone-propagation-delay"] + max(ttl1, ttl2)
|
||||
|
||||
self.timing["PublishCDS"] = self.timing["Published"] + ipubc
|
||||
|
||||
if self.metadata["Lifetime"] != 0:
|
||||
self.timing["DeleteCDS"] = self.timing["PublishCDS"] + int(
|
||||
self.metadata["Lifetime"]
|
||||
)
|
||||
|
||||
def Iret(self, config):
|
||||
if self.metadata["Lifetime"] == 0:
|
||||
return
|
||||
|
||||
sign_delay = config["signatures-validity"] - config["signatures-refresh"]
|
||||
safety_interval = config["retire-safety"]
|
||||
|
||||
iretKSK = timedelta(0)
|
||||
iretZSK = timedelta(0)
|
||||
if self.key.is_ksk():
|
||||
# Iret = DprpP + TTLds
|
||||
iretKSK = (
|
||||
config["parent-propagation-delay"] + config["ds-ttl"] + safety_interval
|
||||
)
|
||||
if self.key.is_zsk():
|
||||
# Iret = Dsgn + Dprp + TTLsig
|
||||
iretZSK = (
|
||||
sign_delay
|
||||
+ config["zone-propagation-delay"]
|
||||
+ config["max-zone-ttl"]
|
||||
+ safety_interval
|
||||
)
|
||||
|
||||
self.timing["Removed"] = self.timing["Retired"] + max(iretKSK, iretZSK)
|
||||
|
||||
def set_expected_keytimes(self, config, offset=None, pregenerated=False):
|
||||
if self.key is None:
|
||||
raise ValueError("KeyProperties must be attached to a Key")
|
||||
|
||||
if self.properties["legacy"]:
|
||||
return
|
||||
|
||||
if offset is None:
|
||||
offset = self.properties["offset"]
|
||||
|
||||
self.timing["Generated"] = self.key.get_timing("Created")
|
||||
|
||||
self.timing["Published"] = self.timing["Generated"]
|
||||
if pregenerated:
|
||||
self.timing["Published"] = self.key.get_timing("Publish")
|
||||
self.timing["Published"] = self.timing["Published"] + offset
|
||||
self.Ipub(config)
|
||||
|
||||
# Set Retired timing metadata if key has lifetime.
|
||||
if self.metadata["Lifetime"] != 0:
|
||||
self.timing["Retired"] = self.timing["Active"] + int(
|
||||
self.metadata["Lifetime"]
|
||||
)
|
||||
|
||||
self.IpubC(config)
|
||||
self.Iret(config)
|
||||
|
||||
# Key state change times must exist, but since we cannot reliably tell
|
||||
# when named made the actual state change, we don't care what the
|
||||
# value is. Set it to None will verify that the metadata exists, but
|
||||
# without actual checking the value.
|
||||
self.timing["DNSKEYChange"] = None
|
||||
|
||||
if self.key.is_ksk():
|
||||
self.timing["DSChange"] = None
|
||||
self.timing["KRRSIGChange"] = None
|
||||
|
||||
if self.key.is_zsk():
|
||||
self.timing["ZRRSIGChange"] = None
|
||||
|
||||
|
||||
@total_ordering
|
||||
class KeyTimingMetadata:
|
||||
"""
|
||||
@@ -279,7 +117,6 @@ class Key:
|
||||
else:
|
||||
self.keydir = Path(keydir)
|
||||
self.path = str(self.keydir / name)
|
||||
self.privatefile = f"{self.path}.private"
|
||||
self.keyfile = f"{self.path}.key"
|
||||
self.statefile = f"{self.path}.state"
|
||||
self.tag = int(self.name[-5:])
|
||||
@@ -302,43 +139,21 @@ class Key:
|
||||
)
|
||||
return None
|
||||
|
||||
def get_metadata(
|
||||
self, metadata: str, file=None, comment=False, must_exist=True
|
||||
) -> str:
|
||||
if file is None:
|
||||
file = self.statefile
|
||||
def get_metadata(self, metadata: str, must_exist=True) -> str:
|
||||
value = "undefined"
|
||||
regex = rf"{metadata}:\s+(\S+).*"
|
||||
if comment:
|
||||
# The expected metadata is prefixed with a ';'.
|
||||
regex = rf";\s+{metadata}:\s+(\S+).*"
|
||||
with open(file, "r", encoding="utf-8") as fp:
|
||||
for line in fp:
|
||||
regex = rf"{metadata}:\s+(.*)"
|
||||
with open(self.statefile, "r", encoding="utf-8") as file:
|
||||
for line in file:
|
||||
match = re.match(regex, line)
|
||||
if match is not None:
|
||||
value = match.group(1)
|
||||
break
|
||||
if must_exist and value == "undefined":
|
||||
raise ValueError(
|
||||
f'metadata "{metadata}" for key "{self.name}" in file "{file}" undefined'
|
||||
'state metadata "{metadata}" for key "{self.name}" undefined'
|
||||
)
|
||||
return value
|
||||
|
||||
def ttl(self) -> int:
|
||||
with open(self.keyfile, "r", encoding="utf-8") as file:
|
||||
for line in file:
|
||||
if line.startswith(";"):
|
||||
continue
|
||||
return int(line.split()[1])
|
||||
return 0
|
||||
|
||||
def dnskey(self):
|
||||
with open(self.keyfile, "r", encoding="utf-8") as file:
|
||||
for line in file:
|
||||
if "DNSKEY" in line:
|
||||
return line.strip()
|
||||
return "undefined"
|
||||
|
||||
def is_ksk(self) -> bool:
|
||||
return self.get_metadata("KSK") == "yes"
|
||||
|
||||
@@ -372,7 +187,7 @@ class Key:
|
||||
dsfromkey_command = [
|
||||
os.environ.get("DSFROMKEY"),
|
||||
"-T",
|
||||
str(self.ttl()),
|
||||
"3600",
|
||||
"-a",
|
||||
alg,
|
||||
"-C",
|
||||
@@ -401,152 +216,6 @@ class Key:
|
||||
|
||||
return digest_fromfile == digest_fromwire
|
||||
|
||||
def has_metadata(self, key, metadata):
|
||||
# If 'key' exists in 'metadata' then it must also exist in the state
|
||||
# meta data. Otherwise, it must not exist in the state meta data.
|
||||
if key in metadata:
|
||||
return self.get_metadata(key) != "undefined"
|
||||
|
||||
value = self.get_metadata(key, must_exist=False)
|
||||
if value != "undefined":
|
||||
isctest.log.debug(f"{self.name} {key} METADATA UNEXPECTED: {value}")
|
||||
return value == "undefined"
|
||||
|
||||
def match_metadata(self, key, metadata):
|
||||
# If 'key' exists in 'metadata' then it must match the value in the
|
||||
# state meta data. Otherwise, it must also not exist in the state meta
|
||||
# data.
|
||||
if key in metadata:
|
||||
value = self.get_metadata(key)
|
||||
if value != f"{metadata[key]}":
|
||||
isctest.log.debug(
|
||||
f"{self.name} {key} METADATA MISMATCH: {value} - {metadata[key]}"
|
||||
)
|
||||
return value == f"{metadata[key]}"
|
||||
|
||||
value = self.get_metadata(key, must_exist=False)
|
||||
if value != "undefined":
|
||||
isctest.log.debug(f"{self.name} {key} METADATA UNEXPECTED: {value}")
|
||||
return value == "undefined"
|
||||
|
||||
def match_timing(self, key, timing, file, comment=False):
|
||||
# If 'key' exists in 'timing' then it must match the value in the
|
||||
# state timing data. Otherwise, it must also not exist in the state timing
|
||||
# data.
|
||||
if key in timing:
|
||||
value = self.get_metadata(key, file=file, comment=comment)
|
||||
if value != str(timing[key]):
|
||||
isctest.log.debug(
|
||||
f"{self.name} {key} TIMING MISMATCH: {value} - {timing[key]}"
|
||||
)
|
||||
return value == str(timing[key])
|
||||
|
||||
value = self.get_metadata(key, file=file, comment=comment, must_exist=False)
|
||||
if value != "undefined":
|
||||
isctest.log.debug(f"{self.name} {key} TIMING UNEXPECTED: {value}")
|
||||
return value == "undefined"
|
||||
|
||||
def match_properties(self, zone, properties):
|
||||
# Check the key with given properties.
|
||||
if not properties.properties["expect"]:
|
||||
return False
|
||||
|
||||
# Check file existence.
|
||||
# Noop. If file is missing then the get_metadata calls will fail.
|
||||
|
||||
# Check the public key file.
|
||||
role = properties.properties["role_full"]
|
||||
comment = f"This is a {role} key, keyid {self.tag}, for {zone}."
|
||||
if not isctest.check.file_contents_contain(self.keyfile, comment):
|
||||
isctest.log.debug(f"{self.name} COMMENT MISMATCH: expected '{comment}'")
|
||||
return False
|
||||
|
||||
ttl = properties.properties["dnskey_ttl"]
|
||||
flags = properties.properties["flags"]
|
||||
alg = properties.metadata["Algorithm"]
|
||||
dnskey = f"{zone}. {ttl} IN DNSKEY {flags} 3 {alg}"
|
||||
if not isctest.check.file_contents_contain(self.keyfile, dnskey):
|
||||
isctest.log.debug(f"{self.name} DNSKEY MISMATCH: expected '{dnskey}'")
|
||||
return False
|
||||
|
||||
# Now check the private key file.
|
||||
if properties.properties["private"]:
|
||||
# Retrieve creation date.
|
||||
created = self.get_metadata("Generated")
|
||||
|
||||
pval = self.get_metadata("Created", file=self.privatefile)
|
||||
if pval != created:
|
||||
isctest.log.debug(
|
||||
f"{self.name} Created METADATA MISMATCH: {pval} - {created}"
|
||||
)
|
||||
return False
|
||||
pval = self.get_metadata("Private-key-format", file=self.privatefile)
|
||||
if pval != "v1.3":
|
||||
isctest.log.debug(
|
||||
f"{self.name} Private-key-format METADATA MISMATCH: {pval} - v1.3"
|
||||
)
|
||||
return False
|
||||
pval = self.get_metadata("Algorithm", file=self.privatefile)
|
||||
if pval != f"{alg}":
|
||||
isctest.log.debug(
|
||||
f"{self.name} Algorithm METADATA MISMATCH: {pval} - {alg}"
|
||||
)
|
||||
return False
|
||||
|
||||
# Now check the key state file.
|
||||
if properties.properties["legacy"]:
|
||||
return True
|
||||
|
||||
comment = f"This is the state of key {self.tag}, for {zone}."
|
||||
if not isctest.check.file_contents_contain(self.statefile, comment):
|
||||
isctest.log.debug(f"{self.name} COMMENT MISMATCH: expected '{comment}'")
|
||||
return False
|
||||
|
||||
attributes = [
|
||||
"Lifetime",
|
||||
"Algorithm",
|
||||
"Length",
|
||||
"KSK",
|
||||
"ZSK",
|
||||
"GoalState",
|
||||
"DNSKEYState",
|
||||
"KRRSIGState",
|
||||
"ZRRSIGState",
|
||||
"DSState",
|
||||
]
|
||||
for key in attributes:
|
||||
if not self.match_metadata(key, properties.metadata):
|
||||
return False
|
||||
|
||||
# A match is found.
|
||||
return True
|
||||
|
||||
def match_timingmetadata(self, timings, file=None, comment=False):
|
||||
if file is None:
|
||||
file = self.statefile
|
||||
|
||||
attributes = [
|
||||
"Generated",
|
||||
"Created",
|
||||
"Published",
|
||||
"Publish",
|
||||
"PublishCDS",
|
||||
"SyncPublish",
|
||||
"Active",
|
||||
"Activate",
|
||||
"Retired",
|
||||
"Inactive",
|
||||
"Revoked",
|
||||
"Removed",
|
||||
"Delete",
|
||||
]
|
||||
for key in attributes:
|
||||
if not self.match_timing(key, timings, file, comment=comment):
|
||||
isctest.log.debug(f"{self.name} TIMING METADATA MISMATCH: {key}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __lt__(self, other: "Key"):
|
||||
return self.name < other.name
|
||||
|
||||
@@ -557,14 +226,14 @@ class Key:
|
||||
return self.path
|
||||
|
||||
|
||||
def check_zone_is_signed(server, zone, tsig=None):
|
||||
def check_zone_is_signed(server, zone):
|
||||
addr = server.ip
|
||||
fqdn = f"{zone}."
|
||||
|
||||
# wait until zone is fully signed
|
||||
signed = False
|
||||
for _ in range(10):
|
||||
response = _query(server, fqdn, dns.rdatatype.NSEC, tsig=tsig)
|
||||
response = _query(server, fqdn, dns.rdatatype.NSEC)
|
||||
if not isinstance(response, dns.message.Message):
|
||||
isctest.log.debug(f"no response for {fqdn} NSEC from {addr}")
|
||||
elif response.rcode() != dns.rcode.NOERROR:
|
||||
@@ -608,111 +277,13 @@ def check_zone_is_signed(server, zone, tsig=None):
|
||||
assert signed
|
||||
|
||||
|
||||
def check_keys(zone, keys, expected):
|
||||
# Checks keys for a configured zone. This verifies:
|
||||
# 1. The expected number of keys exist in 'keys'.
|
||||
# 2. The keys match the expected properties.
|
||||
|
||||
def _check_keys():
|
||||
# check number of keys matches expected.
|
||||
if len(keys) != len(expected):
|
||||
return False
|
||||
|
||||
if len(keys) == 0:
|
||||
return True
|
||||
|
||||
for expect in expected:
|
||||
expect.key = None
|
||||
|
||||
for key in keys:
|
||||
found = False
|
||||
i = 0
|
||||
while not found and i < len(expected):
|
||||
if expected[i].key is None:
|
||||
found = key.match_properties(zone, expected[i])
|
||||
if found:
|
||||
key.external = expected[i].properties["legacy"]
|
||||
expected[i].key = key
|
||||
i += 1
|
||||
if not found:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
isctest.run.retry_with_timeout(_check_keys, timeout=10)
|
||||
|
||||
|
||||
def check_keytimes(keys, expected):
|
||||
# Check the key timing metadata for all keys in 'keys'.
|
||||
assert len(keys) == len(expected)
|
||||
|
||||
if len(keys) == 0:
|
||||
return
|
||||
|
||||
for key in keys:
|
||||
for expect in expected:
|
||||
if expect.properties["legacy"]:
|
||||
continue
|
||||
|
||||
if not key is expect.key:
|
||||
continue
|
||||
|
||||
synonyms = {}
|
||||
if "Generated" in expect.timing:
|
||||
synonyms["Created"] = expect.timing["Generated"]
|
||||
if "Published" in expect.timing:
|
||||
synonyms["Publish"] = expect.timing["Published"]
|
||||
if "PublishCDS" in expect.timing:
|
||||
synonyms["SyncPublish"] = expect.timing["PublishCDS"]
|
||||
if "Active" in expect.timing:
|
||||
synonyms["Activate"] = expect.timing["Active"]
|
||||
if "Retired" in expect.timing:
|
||||
synonyms["Inactive"] = expect.timing["Retired"]
|
||||
if "DeleteCDS" in expect.timing:
|
||||
synonyms["SyncDelete"] = expect.timing["DeleteCDS"]
|
||||
if "Revoked" in expect.timing:
|
||||
synonyms["Revoked"] = expect.timing["Revoked"]
|
||||
if "Removed" in expect.timing:
|
||||
synonyms["Delete"] = expect.timing["Removed"]
|
||||
|
||||
assert key.match_timingmetadata(synonyms, file=key.keyfile, comment=True)
|
||||
if expect.properties["private"]:
|
||||
assert key.match_timingmetadata(synonyms, file=key.privatefile)
|
||||
if not expect.properties["legacy"]:
|
||||
assert key.match_timingmetadata(expect.timing)
|
||||
|
||||
state_changes = [
|
||||
"DNSKEYChange",
|
||||
"KRRSIGChange",
|
||||
"ZRRSIGChange",
|
||||
"DSChange",
|
||||
]
|
||||
for change in state_changes:
|
||||
assert key.has_metadata(change, expect.timing)
|
||||
|
||||
|
||||
def check_keyrelationships(keys, expected):
|
||||
# Check the key relationships (Successor and Predecessor metadata).
|
||||
for key in keys:
|
||||
for expect in expected:
|
||||
if expect.properties["legacy"]:
|
||||
continue
|
||||
|
||||
if not key is expect.key:
|
||||
continue
|
||||
|
||||
relationship_status = ["Predecessor", "Successor"]
|
||||
for status in relationship_status:
|
||||
assert key.match_metadata(status, expect.metadata)
|
||||
|
||||
|
||||
def check_dnssec_verify(server, zone, tsig=None):
|
||||
def check_dnssec_verify(server, zone):
|
||||
# Check if zone if DNSSEC valid with dnssec-verify.
|
||||
fqdn = f"{zone}."
|
||||
|
||||
verified = False
|
||||
for _ in range(10):
|
||||
transfer = _query(server, fqdn, dns.rdatatype.AXFR, tsig=tsig)
|
||||
transfer = _query(server, fqdn, dns.rdatatype.AXFR)
|
||||
if not isinstance(transfer, dns.message.Message):
|
||||
isctest.log.debug(f"no response for {fqdn} AXFR from {server.ip}")
|
||||
elif transfer.rcode() != dns.rcode.NOERROR:
|
||||
@@ -844,9 +415,9 @@ def _check_dnskeys(dnskeys, keys, cdnskey=False):
|
||||
delete_md = f"Sync{delete_md}"
|
||||
|
||||
for key in keys:
|
||||
publish = key.get_timing(publish_md, must_exist=False)
|
||||
publish = key.get_timing(publish_md)
|
||||
delete = key.get_timing(delete_md, must_exist=False)
|
||||
published = publish is not None and now >= publish
|
||||
published = now >= publish
|
||||
removed = delete is not None and delete <= now
|
||||
|
||||
if not published or removed:
|
||||
@@ -931,8 +502,8 @@ def check_cds(rrset, keys):
|
||||
assert numcds == len(cdss)
|
||||
|
||||
|
||||
def _query_rrset(server, fqdn, qtype, tsig=None):
|
||||
response = _query(server, fqdn, qtype, tsig=tsig)
|
||||
def _query_rrset(server, fqdn, qtype):
|
||||
response = _query(server, fqdn, qtype)
|
||||
assert response.rcode() == dns.rcode.NOERROR
|
||||
|
||||
rrs = []
|
||||
@@ -952,43 +523,46 @@ def _query_rrset(server, fqdn, qtype, tsig=None):
|
||||
return rrs, rrsigs
|
||||
|
||||
|
||||
def check_apex(server, zone, ksks, zsks, tsig=None):
|
||||
def check_apex(server, zone, ksks, zsks):
|
||||
# Test the apex of a zone. This checks that the SOA and DNSKEY RRsets
|
||||
# are signed correctly and with the appropriate keys.
|
||||
fqdn = f"{zone}."
|
||||
|
||||
# test dnskey query
|
||||
dnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.DNSKEY, tsig=tsig)
|
||||
dnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.DNSKEY)
|
||||
assert len(dnskeys) > 0
|
||||
check_dnskeys(dnskeys, ksks, zsks)
|
||||
assert len(rrsigs) > 0
|
||||
check_signatures(rrsigs, dns.rdatatype.DNSKEY, fqdn, ksks, zsks)
|
||||
|
||||
# test soa query
|
||||
soa, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.SOA, tsig=tsig)
|
||||
soa, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.SOA)
|
||||
assert len(soa) == 1
|
||||
assert f"{zone}. {DEFAULT_TTL} IN SOA" in soa[0].to_text()
|
||||
assert len(rrsigs) > 0
|
||||
check_signatures(rrsigs, dns.rdatatype.SOA, fqdn, ksks, zsks)
|
||||
|
||||
# test cdnskey query
|
||||
cdnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDNSKEY, tsig=tsig)
|
||||
cdnskeys, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDNSKEY)
|
||||
check_dnskeys(cdnskeys, ksks, zsks, cdnskey=True)
|
||||
if len(cdnskeys) > 0:
|
||||
assert len(rrsigs) > 0
|
||||
check_signatures(rrsigs, dns.rdatatype.CDNSKEY, fqdn, ksks, zsks)
|
||||
|
||||
# test cds query
|
||||
cds, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDS, tsig=tsig)
|
||||
cds, rrsigs = _query_rrset(server, fqdn, dns.rdatatype.CDS)
|
||||
check_cds(cds, ksks)
|
||||
if len(cds) > 0:
|
||||
assert len(rrsigs) > 0
|
||||
check_signatures(rrsigs, dns.rdatatype.CDS, fqdn, ksks, zsks)
|
||||
|
||||
|
||||
def check_subdomain(server, zone, ksks, zsks, tsig=None):
|
||||
def check_subdomain(server, zone, ksks, zsks):
|
||||
# Test an RRset below the apex and verify it is signed correctly.
|
||||
fqdn = f"{zone}."
|
||||
qname = f"a.{zone}."
|
||||
qtype = dns.rdatatype.A
|
||||
response = _query(server, qname, qtype, tsig=tsig)
|
||||
response = _query(server, qname, qtype)
|
||||
assert response.rcode() == dns.rcode.NOERROR
|
||||
|
||||
match = f"{qname} {DEFAULT_TTL} IN A 10.0.0.1"
|
||||
@@ -1001,180 +575,5 @@ def check_subdomain(server, zone, ksks, zsks, tsig=None):
|
||||
else:
|
||||
assert match in rrset.to_text()
|
||||
|
||||
assert len(rrsigs) > 0
|
||||
check_signatures(rrsigs, qtype, fqdn, ksks, zsks)
|
||||
|
||||
|
||||
def check_update_is_signed(server, fqdn, qname, qtype, rdata, ksks, zsks, tsig=None):
|
||||
# Test an RRset below the apex and verify it is updated and signed correctly.
|
||||
response = _query(server, qname, qtype, tsig=tsig)
|
||||
|
||||
if response.rcode() != dns.rcode.NOERROR:
|
||||
return False
|
||||
|
||||
rrtype = dns.rdatatype.to_text(qtype)
|
||||
match = f"{qname} {DEFAULT_TTL} IN {rrtype} {rdata}"
|
||||
rrsigs = []
|
||||
for rrset in response.answer:
|
||||
if rrset.match(
|
||||
dns.name.from_text(qname), dns.rdataclass.IN, dns.rdatatype.RRSIG, qtype
|
||||
):
|
||||
rrsigs.append(rrset)
|
||||
elif not match in rrset.to_text():
|
||||
return False
|
||||
|
||||
if len(rrsigs) == 0:
|
||||
return False
|
||||
|
||||
# Zone is updated, ready to verify the signatures.
|
||||
check_signatures(rrsigs, qtype, fqdn, ksks, zsks)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def check_next_key_event(server, zone, next_event):
|
||||
if next_event is None:
|
||||
# No next key event check.
|
||||
return True
|
||||
|
||||
val = int(next_event.total_seconds())
|
||||
if val == 3600:
|
||||
waitfor = rf".*zone {zone}.*: next key event in (.*) seconds"
|
||||
else:
|
||||
# Don't want default loadkeys interval.
|
||||
waitfor = rf".*zone {zone}.*: next key event in (?!3600$)(.*) seconds"
|
||||
|
||||
with server.watch_log_from_start() as watcher:
|
||||
watcher.wait_for_line(re.compile(waitfor))
|
||||
|
||||
next_found = False
|
||||
minval = val - NEXT_KEY_EVENT_THRESHOLD
|
||||
maxval = val + NEXT_KEY_EVENT_THRESHOLD
|
||||
with open(f"{server.identifier}/named.run", "r", encoding="utf-8") as fp:
|
||||
for line in fp:
|
||||
match = re.match(waitfor, line)
|
||||
if match is not None:
|
||||
nextval = int(match.group(1))
|
||||
if minval <= nextval <= maxval:
|
||||
next_found = True
|
||||
break
|
||||
|
||||
isctest.log.debug(
|
||||
f"check next key event: expected {val} in: {line.strip()}"
|
||||
)
|
||||
|
||||
return next_found
|
||||
|
||||
|
||||
def keydir_to_keylist(
|
||||
zone: str, keydir: Optional[str] = None, in_use: Optional[bool] = False
|
||||
) -> List[Key]:
|
||||
# Retrieve all keys from the key files in a directory. If 'zone' is None,
|
||||
# retrieve all keys in the directory, otherwise only those matching the
|
||||
# zone name. If 'keydir' is None, search the current directory.
|
||||
if zone is None:
|
||||
zone = ""
|
||||
|
||||
all_keys = []
|
||||
if keydir is None:
|
||||
regex = rf"(K{zone}\.\+.*\+.*)\.key"
|
||||
for filename in glob.glob(f"K{zone}.+*+*.key"):
|
||||
match = re.match(regex, filename)
|
||||
if match is not None:
|
||||
all_keys.append(Key(match.group(1)))
|
||||
else:
|
||||
regex = rf"{keydir}/(K{zone}\.\+.*\+.*)\.key"
|
||||
for filename in glob.glob(f"{keydir}/K{zone}.+*+*.key"):
|
||||
match = re.match(regex, filename)
|
||||
if match is not None:
|
||||
all_keys.append(Key(match.group(1), keydir))
|
||||
|
||||
states = ["GoalState", "DNSKEYState", "KRRSIGState", "ZRRSIGState", "DSState"]
|
||||
|
||||
def used(kk):
|
||||
if not in_use:
|
||||
return True
|
||||
|
||||
for state in states:
|
||||
val = kk.get_metadata(state, must_exist=False)
|
||||
if val not in ["undefined", "hidden"]:
|
||||
isctest.log.debug(f"key {kk} in use")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
return [k for k in all_keys if used(k)]
|
||||
|
||||
|
||||
def keystr_to_keylist(keystr: str, keydir: Optional[str] = None) -> List[Key]:
|
||||
return [Key(name, keydir) for name in keystr.split()]
|
||||
|
||||
|
||||
def policy_to_properties(ttl, keys: List[str]) -> List[KeyProperties]:
|
||||
# Get the policies from a list of specially formatted strings.
|
||||
# The splitted line should result in the following items:
|
||||
# line[0]: Role
|
||||
# line[1]: Lifetime
|
||||
# line[2]: Algorithm
|
||||
# line[3]: Length
|
||||
# Then, optional data for specific tests may follow:
|
||||
# - "goal", "dnskey", "krrsig", "zrrsig", "ds", followed by a value,
|
||||
# sets the given state to the specific value
|
||||
# - "offset", an offset for testing key rollover timings
|
||||
proplist = []
|
||||
count = 0
|
||||
for key in keys:
|
||||
count += 1
|
||||
line = key.split()
|
||||
keyprop = KeyProperties(f"KEY{count}", {}, {}, {})
|
||||
keyprop.properties["expect"] = True
|
||||
keyprop.properties["private"] = True
|
||||
keyprop.properties["legacy"] = False
|
||||
keyprop.properties["offset"] = timedelta(0)
|
||||
keyprop.properties["role"] = line[0]
|
||||
if line[0] == "zsk":
|
||||
keyprop.properties["role_full"] = "zone-signing"
|
||||
keyprop.properties["flags"] = 256
|
||||
keyprop.metadata["ZSK"] = "yes"
|
||||
keyprop.metadata["KSK"] = "no"
|
||||
else:
|
||||
keyprop.properties["role_full"] = "key-signing"
|
||||
keyprop.properties["flags"] = 257
|
||||
keyprop.metadata["ZSK"] = "yes" if line[0] == "csk" else "no"
|
||||
keyprop.metadata["KSK"] = "yes"
|
||||
|
||||
keyprop.properties["dnskey_ttl"] = ttl
|
||||
keyprop.metadata["Algorithm"] = line[2]
|
||||
keyprop.metadata["Length"] = line[3]
|
||||
keyprop.metadata["Lifetime"] = 0
|
||||
if line[1] != "unlimited":
|
||||
keyprop.metadata["Lifetime"] = int(line[1])
|
||||
|
||||
if len(line) > 4:
|
||||
i = 4
|
||||
while i < len(line):
|
||||
if line[i].startswith("goal:"):
|
||||
keyval = line[i].split(":")
|
||||
keyprop.metadata["GoalState"] = keyval[1]
|
||||
elif line[i].startswith("dnskey:"):
|
||||
keyval = line[i].split(":")
|
||||
keyprop.metadata["DNSKEYState"] = keyval[1]
|
||||
elif line[i].startswith("krrsig:"):
|
||||
keyval = line[i].split(":")
|
||||
keyprop.metadata["KRRSIGState"] = keyval[1]
|
||||
elif line[i].startswith("zrrsig:"):
|
||||
keyval = line[i].split(":")
|
||||
keyprop.metadata["ZRRSIGState"] = keyval[1]
|
||||
elif line[i].startswith("ds:"):
|
||||
keyval = line[i].split(":")
|
||||
keyprop.metadata["DSState"] = keyval[1]
|
||||
elif line[i].startswith("offset:"):
|
||||
keyval = line[i].split(":")
|
||||
keyprop.properties["offset"] = timedelta(seconds=int(keyval[1]))
|
||||
else:
|
||||
assert False, f"undefined optional data {line[i]}"
|
||||
|
||||
i += 1
|
||||
|
||||
proplist.append(keyprop)
|
||||
|
||||
return proplist
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# 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.
|
||||
|
||||
. ../conf.sh
|
||||
|
||||
if test -n "$PYTHON"; then
|
||||
if $PYTHON -c "from dns.update import UpdateMessage" 2>/dev/null; then
|
||||
:
|
||||
else
|
||||
echo_i "This test requires the dnspython >= 2.0.0 module." >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo_i "This test requires Python and the dnspython module." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -54,6 +54,178 @@ next_key_event_threshold=100
|
||||
# Tests #
|
||||
###############################################################################
|
||||
|
||||
#
|
||||
# dnssec-keygen
|
||||
#
|
||||
set_zone "kasp"
|
||||
set_policy "kasp" "4" "200"
|
||||
set_server "keys" "10.53.0.1"
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "check that 'dnssec-keygen -k' (configured policy) creates valid files ($n)"
|
||||
ret=0
|
||||
$KEYGEN -K keys -k "$POLICY" -l kasp.conf "$ZONE" >"keygen.out.$POLICY.test$n" 2>/dev/null || ret=1
|
||||
lines=$(wc -l <"keygen.out.$POLICY.test$n")
|
||||
test "$lines" -eq $NUM_KEYS || log_error "wrong number of keys created for policy kasp: $lines"
|
||||
# Temporarily don't log errors because we are searching multiple files.
|
||||
disable_logerror
|
||||
|
||||
# Key properties.
|
||||
set_keyrole "KEY1" "csk"
|
||||
set_keylifetime "KEY1" "31536000"
|
||||
set_keyalgorithm "KEY1" "13" "ECDSAP256SHA256" "256"
|
||||
set_keysigning "KEY1" "yes"
|
||||
set_zonesigning "KEY1" "yes"
|
||||
|
||||
set_keyrole "KEY2" "ksk"
|
||||
set_keylifetime "KEY2" "31536000"
|
||||
set_keyalgorithm "KEY2" "8" "RSASHA256" "2048"
|
||||
set_keysigning "KEY2" "yes"
|
||||
set_zonesigning "KEY2" "no"
|
||||
|
||||
set_keyrole "KEY3" "zsk"
|
||||
set_keylifetime "KEY3" "2592000"
|
||||
set_keyalgorithm "KEY3" "8" "RSASHA256" "2048"
|
||||
set_keysigning "KEY3" "no"
|
||||
set_zonesigning "KEY3" "yes"
|
||||
|
||||
set_keyrole "KEY4" "zsk"
|
||||
set_keylifetime "KEY4" "16070400"
|
||||
set_keyalgorithm "KEY4" "8" "RSASHA256" "3072"
|
||||
set_keysigning "KEY4" "no"
|
||||
set_zonesigning "KEY4" "yes"
|
||||
|
||||
lines=$(get_keyids "$DIR" "$ZONE" | wc -l)
|
||||
test "$lines" -eq $NUM_KEYS || log_error "bad number of key ids"
|
||||
status=$((status + ret))
|
||||
|
||||
ids=$(get_keyids "$DIR" "$ZONE")
|
||||
for id in $ids; do
|
||||
# There are four key files with the same algorithm.
|
||||
# Check them until a match is found.
|
||||
ret=0 && check_key "KEY1" "$id"
|
||||
test "$ret" -eq 0 && continue
|
||||
|
||||
ret=0 && check_key "KEY2" "$id"
|
||||
test "$ret" -eq 0 && continue
|
||||
|
||||
ret=0 && check_key "KEY3" "$id"
|
||||
test "$ret" -eq 0 && continue
|
||||
|
||||
ret=0 && check_key "KEY4" "$id"
|
||||
|
||||
# If ret is still non-zero, non of the files matched.
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
done
|
||||
# Turn error logs on again.
|
||||
enable_logerror
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "check that 'dnssec-keygen -k' (default policy) creates valid files ($n)"
|
||||
ret=0
|
||||
set_zone "kasp"
|
||||
set_policy "default" "1" "3600"
|
||||
set_server "." "10.53.0.1"
|
||||
# Key properties.
|
||||
key_clear "KEY1"
|
||||
set_keyrole "KEY1" "csk"
|
||||
set_keylifetime "KEY1" "0"
|
||||
set_keyalgorithm "KEY1" "13" "ECDSAP256SHA256" "256"
|
||||
set_keysigning "KEY1" "yes"
|
||||
set_zonesigning "KEY1" "yes"
|
||||
|
||||
key_clear "KEY2"
|
||||
key_clear "KEY3"
|
||||
key_clear "KEY4"
|
||||
|
||||
$KEYGEN -G -k "$POLICY" "$ZONE" >"keygen.out.$POLICY.test$n" 2>/dev/null || ret=1
|
||||
lines=$(wc -l <"keygen.out.$POLICY.test$n")
|
||||
test "$lines" -eq $NUM_KEYS || log_error "wrong number of keys created for policy default: $lines"
|
||||
# Temporarily adjust max search depth for this test
|
||||
MAXDEPTH=1
|
||||
ids=$(get_keyids "$DIR" "$ZONE")
|
||||
MAXDEPTH=3
|
||||
echo_i "found in dir $DIR for zone $ZONE the following keytags: $ids"
|
||||
for id in $ids; do
|
||||
check_key "KEY1" "$id"
|
||||
test "$ret" -eq 0 && key_save KEY1
|
||||
check_keytimes
|
||||
done
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
#
|
||||
# dnssec-settime
|
||||
#
|
||||
|
||||
# These test builds upon the latest created key with dnssec-keygen and uses the
|
||||
# environment variables BASE_FILE, KEY_FILE, PRIVATE_FILE and STATE_FILE.
|
||||
CMP_FILE="${BASE_FILE}.cmp"
|
||||
n=$((n + 1))
|
||||
echo_i "check that 'dnssec-settime' by default does not edit key state file ($n)"
|
||||
ret=0
|
||||
cp "$STATE_FILE" "$CMP_FILE"
|
||||
$SETTIME -P +3600 "$BASE_FILE" >/dev/null || log_error "settime failed"
|
||||
grep "; Publish: " "$KEY_FILE" >/dev/null || log_error "mismatch published in $KEY_FILE"
|
||||
grep "Publish: " "$PRIVATE_FILE" >/dev/null || log_error "mismatch published in $PRIVATE_FILE"
|
||||
diff "$CMP_FILE" "$STATE_FILE" || log_error "unexpected file change in $STATE_FILE"
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "check that 'dnssec-settime -s' also sets publish time metadata and states in key state file ($n)"
|
||||
ret=0
|
||||
cp "$STATE_FILE" "$CMP_FILE"
|
||||
now=$(date +%Y%m%d%H%M%S)
|
||||
$SETTIME -s -P "$now" -g "omnipresent" -k "rumoured" "$now" -z "omnipresent" "$now" -r "rumoured" "$now" -d "hidden" "$now" "$BASE_FILE" >/dev/null || log_error "settime failed"
|
||||
set_keystate "KEY1" "GOAL" "omnipresent"
|
||||
set_keystate "KEY1" "STATE_DNSKEY" "rumoured"
|
||||
set_keystate "KEY1" "STATE_KRRSIG" "rumoured"
|
||||
set_keystate "KEY1" "STATE_ZRRSIG" "omnipresent"
|
||||
set_keystate "KEY1" "STATE_DS" "hidden"
|
||||
check_key "KEY1" "$id"
|
||||
test "$ret" -eq 0 && key_save KEY1
|
||||
set_keytime "KEY1" "PUBLISHED" "${now}"
|
||||
check_keytimes
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "check that 'dnssec-settime -s' also unsets publish time metadata and states in key state file ($n)"
|
||||
ret=0
|
||||
cp "$STATE_FILE" "$CMP_FILE"
|
||||
$SETTIME -s -P "none" -g "none" -k "none" "$now" -z "none" "$now" -r "none" "$now" -d "none" "$now" "$BASE_FILE" >/dev/null || log_error "settime failed"
|
||||
set_keystate "KEY1" "GOAL" "none"
|
||||
set_keystate "KEY1" "STATE_DNSKEY" "none"
|
||||
set_keystate "KEY1" "STATE_KRRSIG" "none"
|
||||
set_keystate "KEY1" "STATE_ZRRSIG" "none"
|
||||
set_keystate "KEY1" "STATE_DS" "none"
|
||||
check_key "KEY1" "$id"
|
||||
test "$ret" -eq 0 && key_save KEY1
|
||||
set_keytime "KEY1" "PUBLISHED" "none"
|
||||
check_keytimes
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "check that 'dnssec-settime -s' also sets active time metadata and states in key state file (uppercase) ($n)"
|
||||
ret=0
|
||||
cp "$STATE_FILE" "$CMP_FILE"
|
||||
now=$(date +%Y%m%d%H%M%S)
|
||||
$SETTIME -s -A "$now" -g "HIDDEN" -k "UNRETENTIVE" "$now" -z "UNRETENTIVE" "$now" -r "OMNIPRESENT" "$now" -d "OMNIPRESENT" "$now" "$BASE_FILE" >/dev/null || log_error "settime failed"
|
||||
set_keystate "KEY1" "GOAL" "hidden"
|
||||
set_keystate "KEY1" "STATE_DNSKEY" "unretentive"
|
||||
set_keystate "KEY1" "STATE_KRRSIG" "omnipresent"
|
||||
set_keystate "KEY1" "STATE_ZRRSIG" "unretentive"
|
||||
set_keystate "KEY1" "STATE_DS" "omnipresent"
|
||||
check_key "KEY1" "$id"
|
||||
test "$ret" -eq 0 && key_save KEY1
|
||||
set_keytime "KEY1" "ACTIVE" "${now}"
|
||||
check_keytimes
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
#
|
||||
# named
|
||||
#
|
||||
@@ -64,7 +236,6 @@ next_key_event_threshold=100
|
||||
# infinite loops if there is an error.
|
||||
n=$((n + 1))
|
||||
echo_i "waiting for kasp signing changes to take effect ($n)"
|
||||
ret=0
|
||||
|
||||
_wait_for_done_apexnsec() {
|
||||
while read -r zone; do
|
||||
@@ -85,6 +256,18 @@ retry_quiet 30 _wait_for_done_apexnsec || ret=1
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
# Test max-zone-ttl rejects zones with too high TTL.
|
||||
n=$((n + 1))
|
||||
echo_i "check that max-zone-ttl rejects zones with too high TTL ($n)"
|
||||
ret=0
|
||||
set_zone "max-zone-ttl.kasp"
|
||||
grep "loading from master file ${ZONE}.db failed: out of range" "ns3/named.run" >/dev/null || ret=1
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
#
|
||||
# Zone: default.kasp.
|
||||
#
|
||||
set_keytimes_csk_policy() {
|
||||
# The first key is immediately published and activated.
|
||||
created=$(key_get KEY1 CREATED)
|
||||
@@ -97,6 +280,10 @@ set_keytimes_csk_policy() {
|
||||
# Key lifetime is unlimited, so not setting RETIRED and REMOVED.
|
||||
}
|
||||
|
||||
# Check the zone with default kasp policy has loaded and is signed.
|
||||
set_zone "default.kasp"
|
||||
set_policy "default" "1" "3600"
|
||||
set_server "ns3" "10.53.0.3"
|
||||
# Key properties.
|
||||
set_keyrole "KEY1" "csk"
|
||||
set_keylifetime "KEY1" "0"
|
||||
@@ -110,6 +297,240 @@ set_keystate "KEY1" "STATE_KRRSIG" "rumoured"
|
||||
set_keystate "KEY1" "STATE_ZRRSIG" "rumoured"
|
||||
set_keystate "KEY1" "STATE_DS" "hidden"
|
||||
|
||||
check_keys
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
set_keytimes_csk_policy
|
||||
check_keytimes
|
||||
check_apex
|
||||
check_subdomain
|
||||
dnssec_verify
|
||||
|
||||
# Trigger a keymgr run. Make sure the key files are not touched if there are
|
||||
# no modifications to the key metadata.
|
||||
n=$((n + 1))
|
||||
echo_i "make sure key files are untouched if metadata does not change ($n)"
|
||||
ret=0
|
||||
basefile=$(key_get KEY1 BASEFILE)
|
||||
privkey_stat=$(key_get KEY1 PRIVKEY_STAT)
|
||||
pubkey_stat=$(key_get KEY1 PUBKEY_STAT)
|
||||
state_stat=$(key_get KEY1 STATE_STAT)
|
||||
|
||||
nextpart $DIR/named.run >/dev/null
|
||||
rndccmd 10.53.0.3 loadkeys "$ZONE" >/dev/null || log_error "rndc loadkeys zone ${ZONE} failed"
|
||||
wait_for_log 3 "keymgr: $ZONE done" $DIR/named.run || ret=1
|
||||
privkey_stat2=$(key_stat "${basefile}.private")
|
||||
pubkey_stat2=$(key_stat "${basefile}.key")
|
||||
state_stat2=$(key_stat "${basefile}.state")
|
||||
test "$privkey_stat" = "$privkey_stat2" || log_error "wrong private key file stat (expected $privkey_stat got $privkey_stat2)"
|
||||
test "$pubkey_stat" = "$pubkey_stat2" || log_error "wrong public key file stat (expected $pubkey_stat got $pubkey_stat2)"
|
||||
test "$state_stat" = "$state_stat2" || log_error "wrong state file stat (expected $state_stat got $state_stat2)"
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "again ($n)"
|
||||
ret=0
|
||||
|
||||
nextpart $DIR/named.run >/dev/null
|
||||
rndccmd 10.53.0.3 loadkeys "$ZONE" >/dev/null || log_error "rndc loadkeys zone ${ZONE} failed"
|
||||
wait_for_log 3 "keymgr: $ZONE done" $DIR/named.run || ret=1
|
||||
privkey_stat2=$(key_stat "${basefile}.private")
|
||||
pubkey_stat2=$(key_stat "${basefile}.key")
|
||||
state_stat2=$(key_stat "${basefile}.state")
|
||||
test "$privkey_stat" = "$privkey_stat2" || log_error "wrong private key file stat (expected $privkey_stat got $privkey_stat2)"
|
||||
test "$pubkey_stat" = "$pubkey_stat2" || log_error "wrong public key file stat (expected $pubkey_stat got $pubkey_stat2)"
|
||||
test "$state_stat" = "$state_stat2" || log_error "wrong state file stat (expected $state_stat got $state_stat2)"
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
# Update zone.
|
||||
n=$((n + 1))
|
||||
echo_i "modify unsigned zone file and check that new record is signed for zone ${ZONE} ($n)"
|
||||
ret=0
|
||||
cp "${DIR}/template2.db.in" "${DIR}/${ZONE}.db"
|
||||
rndccmd 10.53.0.3 reload "$ZONE" >/dev/null || log_error "rndc reload zone ${ZONE} failed"
|
||||
|
||||
update_is_signed() {
|
||||
ip_a=$1
|
||||
ip_d=$2
|
||||
|
||||
if [ "$ip_a" != "-" ]; then
|
||||
dig_with_opts "a.${ZONE}" "@${SERVER}" A >"dig.out.$DIR.test$n.a" || return 1
|
||||
grep "status: NOERROR" "dig.out.$DIR.test$n.a" >/dev/null || return 1
|
||||
grep "a.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*${ip_a}" "dig.out.$DIR.test$n.a" >/dev/null || return 1
|
||||
lines=$(get_keys_which_signed A 0 "dig.out.$DIR.test$n.a" | wc -l)
|
||||
test "$lines" -eq 1 || return 1
|
||||
get_keys_which_signed A 0 "dig.out.$DIR.test$n.a" | grep "^${KEY_ID}$" >/dev/null || return 1
|
||||
fi
|
||||
|
||||
if [ "$ip_d" != "-" ]; then
|
||||
dig_with_opts "d.${ZONE}" "@${SERVER}" A >"dig.out.$DIR.test$n".d || return 1
|
||||
grep "status: NOERROR" "dig.out.$DIR.test$n".d >/dev/null || return 1
|
||||
grep "d.${ZONE}\..*${DEFAULT_TTL}.*IN.*A.*${ip_d}" "dig.out.$DIR.test$n".d >/dev/null || return 1
|
||||
lines=$(get_keys_which_signed A 0 "dig.out.$DIR.test$n".d | wc -l)
|
||||
test "$lines" -eq 1 || return 1
|
||||
get_keys_which_signed A 0 "dig.out.$DIR.test$n".d | grep "^${KEY_ID}$" >/dev/null || return 1
|
||||
fi
|
||||
}
|
||||
|
||||
retry_quiet 10 update_is_signed "10.0.0.11" "10.0.0.44" || ret=1
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
# Move the private key file, a rekey event should not introduce replacement
|
||||
# keys.
|
||||
ret=0
|
||||
echo_i "test that if private key files are inaccessible this doesn't trigger a rollover ($n)"
|
||||
basefile=$(key_get KEY1 BASEFILE)
|
||||
mv "${basefile}.private" "${basefile}.offline"
|
||||
rndccmd 10.53.0.3 loadkeys "$ZONE" >/dev/null || log_error "rndc loadkeys zone ${ZONE} failed"
|
||||
wait_for_log 3 "zone $ZONE/IN (signed): zone_rekey:zone_verifykeys failed: some key files are missing" $DIR/named.run || ret=1
|
||||
mv "${basefile}.offline" "${basefile}.private"
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
# Nothing has changed.
|
||||
check_keys
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
set_keytimes_csk_policy
|
||||
check_keytimes
|
||||
check_apex
|
||||
check_subdomain
|
||||
dnssec_verify
|
||||
|
||||
#
|
||||
# A zone with special characters.
|
||||
#
|
||||
set_zone "i-am.\":\;?&[]\@!\$*+,|=\.\(\)special.kasp."
|
||||
set_policy "default" "1" "3600"
|
||||
set_server "ns3" "10.53.0.3"
|
||||
# It is non-trivial to adapt the tests to deal with all possible different
|
||||
# escaping characters, so we will just try to verify the zone.
|
||||
dnssec_verify
|
||||
|
||||
#
|
||||
# Zone: dynamic.kasp
|
||||
#
|
||||
set_zone "dynamic.kasp"
|
||||
set_dynamic
|
||||
set_policy "default" "1" "3600"
|
||||
set_server "ns3" "10.53.0.3"
|
||||
# Key properties, timings and states same as above.
|
||||
check_keys
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
set_keytimes_csk_policy
|
||||
check_keytimes
|
||||
check_apex
|
||||
check_subdomain
|
||||
dnssec_verify
|
||||
|
||||
# Update zone with nsupdate.
|
||||
n=$((n + 1))
|
||||
echo_i "nsupdate zone and check that new record is signed for zone ${ZONE} ($n)"
|
||||
ret=0
|
||||
(
|
||||
echo zone ${ZONE}
|
||||
echo server 10.53.0.3 "$PORT"
|
||||
echo update del "a.${ZONE}" 300 A 10.0.0.1
|
||||
echo update add "a.${ZONE}" 300 A 10.0.0.101
|
||||
echo update add "d.${ZONE}" 300 A 10.0.0.4
|
||||
echo send
|
||||
) | $NSUPDATE
|
||||
|
||||
retry_quiet 10 update_is_signed "10.0.0.101" "10.0.0.4" || ret=1
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
# Update zone with nsupdate (reverting the above change).
|
||||
n=$((n + 1))
|
||||
echo_i "nsupdate zone and check that new record is signed for zone ${ZONE} ($n)"
|
||||
ret=0
|
||||
(
|
||||
echo zone ${ZONE}
|
||||
echo server 10.53.0.3 "$PORT"
|
||||
echo update add "a.${ZONE}" 300 A 10.0.0.1
|
||||
echo update del "a.${ZONE}" 300 A 10.0.0.101
|
||||
echo update del "d.${ZONE}" 300 A 10.0.0.4
|
||||
echo send
|
||||
) | $NSUPDATE
|
||||
|
||||
retry_quiet 10 update_is_signed "10.0.0.1" "-" || ret=1
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
# Update zone with freeze/thaw.
|
||||
n=$((n + 1))
|
||||
echo_i "modify zone file and check that new record is signed for zone ${ZONE} ($n)"
|
||||
ret=0
|
||||
rndccmd 10.53.0.3 freeze "$ZONE" >/dev/null || log_error "rndc freeze zone ${ZONE} failed"
|
||||
sleep 1
|
||||
echo "d.${ZONE}. 300 A 10.0.0.44" >>"${DIR}/${ZONE}.db"
|
||||
rndccmd 10.53.0.3 thaw "$ZONE" >/dev/null || log_error "rndc thaw zone ${ZONE} failed"
|
||||
|
||||
retry_quiet 10 update_is_signed "10.0.0.1" "10.0.0.44" || ret=1
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
#
|
||||
# Zone: dynamic-inline-signing.kasp
|
||||
#
|
||||
set_zone "dynamic-inline-signing.kasp"
|
||||
set_dynamic
|
||||
set_policy "default" "1" "3600"
|
||||
set_server "ns3" "10.53.0.3"
|
||||
# Key properties, timings and states same as above.
|
||||
check_keys
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
set_keytimes_csk_policy
|
||||
check_keytimes
|
||||
check_apex
|
||||
check_subdomain
|
||||
dnssec_verify
|
||||
|
||||
# Update zone with freeze/thaw.
|
||||
n=$((n + 1))
|
||||
echo_i "modify unsigned zone file and check that new record is signed for zone ${ZONE} ($n)"
|
||||
ret=0
|
||||
rndccmd 10.53.0.3 freeze "$ZONE" >/dev/null || log_error "rndc freeze zone ${ZONE} failed"
|
||||
sleep 1
|
||||
cp "${DIR}/template2.db.in" "${DIR}/${ZONE}.db"
|
||||
rndccmd 10.53.0.3 thaw "$ZONE" >/dev/null || log_error "rndc thaw zone ${ZONE} failed"
|
||||
|
||||
retry_quiet 10 update_is_signed || ret=1
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
#
|
||||
# Zone: dynamic-signed-inline-signing.kasp
|
||||
#
|
||||
set_zone "dynamic-signed-inline-signing.kasp"
|
||||
set_dynamic
|
||||
set_policy "default" "1" "3600"
|
||||
set_server "ns3" "10.53.0.3"
|
||||
dnssec_verify
|
||||
# Ensure no zone_resigninc for the unsigned version of the zone is triggered.
|
||||
n=$((n + 1))
|
||||
echo_i "check if resigning the raw version of the zone is prevented for zone ${ZONE} ($n)"
|
||||
ret=0
|
||||
grep "zone_resigninc: zone $ZONE/IN (unsigned): enter" $DIR/named.run && ret=1
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
#
|
||||
# Zone: inline-signing.kasp
|
||||
#
|
||||
set_zone "inline-signing.kasp"
|
||||
set_policy "default" "1" "3600"
|
||||
set_server "ns3" "10.53.0.3"
|
||||
# Key properties, timings and states same as above.
|
||||
check_keys
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
set_keytimes_csk_policy
|
||||
check_keytimes
|
||||
check_apex
|
||||
check_subdomain
|
||||
dnssec_verify
|
||||
|
||||
#
|
||||
# Zone: checkds-ksk.kasp.
|
||||
#
|
||||
@@ -455,16 +876,53 @@ if [ $RSASHA1_SUPPORTED = 1 ]; then
|
||||
dnssec_verify
|
||||
fi
|
||||
|
||||
#
|
||||
# Zone: unsigned.kasp.
|
||||
#
|
||||
set_zone "unsigned.kasp"
|
||||
set_policy "none" "0" "0"
|
||||
set_server "ns3" "10.53.0.3"
|
||||
|
||||
key_clear "KEY1"
|
||||
key_clear "KEY2"
|
||||
key_clear "KEY3"
|
||||
key_clear "KEY4"
|
||||
|
||||
check_keys
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
check_apex
|
||||
check_subdomain
|
||||
# Make sure the zone file is untouched.
|
||||
n=$((n + 1))
|
||||
echo_i "Make sure the zonefile for zone ${ZONE} is not edited ($n)"
|
||||
ret=0
|
||||
diff "${DIR}/${ZONE}.db.infile" "${DIR}/${ZONE}.db" || ret=1
|
||||
test "$ret" -eq 0 || echo_i "failed"
|
||||
status=$((status + ret))
|
||||
|
||||
#
|
||||
# Zone: insecure.kasp.
|
||||
#
|
||||
set_zone "insecure.kasp"
|
||||
set_policy "insecure" "0" "0"
|
||||
set_server "ns3" "10.53.0.3"
|
||||
|
||||
key_clear "KEY1"
|
||||
key_clear "KEY2"
|
||||
key_clear "KEY3"
|
||||
key_clear "KEY4"
|
||||
|
||||
check_keys
|
||||
check_dnssecstatus "$SERVER" "$POLICY" "$ZONE"
|
||||
check_apex
|
||||
check_subdomain
|
||||
|
||||
#
|
||||
# Zone: unlimited.kasp.
|
||||
#
|
||||
set_zone "unlimited.kasp"
|
||||
set_policy "unlimited" "1" "1234"
|
||||
set_server "ns3" "10.53.0.3"
|
||||
key_clear "KEY1"
|
||||
key_clear "KEY2"
|
||||
key_clear "KEY3"
|
||||
key_clear "KEY4"
|
||||
# Key properties.
|
||||
set_keyrole "KEY1" "csk"
|
||||
set_keylifetime "KEY1" "0"
|
||||
|
||||
@@ -1,569 +0,0 @@
|
||||
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
import dns
|
||||
import dns.update
|
||||
import pytest
|
||||
|
||||
import isctest
|
||||
from isctest.kasp import (
|
||||
KeyProperties,
|
||||
KeyTimingMetadata,
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.extra_artifacts(
|
||||
[
|
||||
"K*.private",
|
||||
"K*.backup",
|
||||
"K*.cmp",
|
||||
"K*.key",
|
||||
"K*.state",
|
||||
"*.axfr",
|
||||
"*.created",
|
||||
"dig.out*",
|
||||
"keyevent.out.*",
|
||||
"keygen.out.*",
|
||||
"keys",
|
||||
"published.test*",
|
||||
"python.out.*",
|
||||
"retired.test*",
|
||||
"rndc.dnssec.*.out.*",
|
||||
"rndc.zonestatus.out.*",
|
||||
"rrsig.out.*",
|
||||
"created.key-*",
|
||||
"unused.key-*",
|
||||
"verify.out.*",
|
||||
"zone.out.*",
|
||||
"ns*/K*.key",
|
||||
"ns*/K*.offline",
|
||||
"ns*/K*.private",
|
||||
"ns*/K*.state",
|
||||
"ns*/*.db",
|
||||
"ns*/*.db.infile",
|
||||
"ns*/*.db.signed",
|
||||
"ns*/*.db.signed.tmp",
|
||||
"ns*/*.jbk",
|
||||
"ns*/*.jnl",
|
||||
"ns*/*.zsk1",
|
||||
"ns*/*.zsk2",
|
||||
"ns*/dsset-*",
|
||||
"ns*/keygen.out.*",
|
||||
"ns*/keys",
|
||||
"ns*/ksk",
|
||||
"ns*/ksk/K*",
|
||||
"ns*/zsk",
|
||||
"ns*/zsk",
|
||||
"ns*/zsk/K*",
|
||||
"ns*/named-fips.conf",
|
||||
"ns*/settime.out.*",
|
||||
"ns*/signer.out.*",
|
||||
"ns*/zones",
|
||||
"ns*/policies/*.conf",
|
||||
"ns3/legacy-keys.*",
|
||||
"ns3/dynamic-signed-inline-signing.kasp.db.signed.signed",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def check_all(server, zone, policy, ksks, zsks, tsig=None):
|
||||
isctest.kasp.check_dnssecstatus(server, zone, ksks + zsks, policy=policy)
|
||||
isctest.kasp.check_apex(server, zone, ksks, zsks, tsig=tsig)
|
||||
isctest.kasp.check_subdomain(server, zone, ksks, zsks, tsig=tsig)
|
||||
isctest.kasp.check_dnssec_verify(server, zone)
|
||||
|
||||
|
||||
def set_keytimes_default_policy(kp):
|
||||
# The first key is immediately published and activated.
|
||||
kp.timing["Generated"] = kp.key.get_timing("Created")
|
||||
kp.timing["Published"] = kp.timing["Generated"]
|
||||
kp.timing["Active"] = kp.timing["Generated"]
|
||||
# The DS can be published if the DNSKEY and RRSIG records are
|
||||
# OMNIPRESENT. This happens after max-zone-ttl (1d) plus
|
||||
# plus zone-propagation-delay (300s).
|
||||
kp.timing["PublishCDS"] = kp.timing["Published"] + timedelta(days=1, seconds=300)
|
||||
# Key lifetime is unlimited, so not setting 'Retired' nor 'Removed'.
|
||||
kp.timing["DNSKEYChange"] = kp.timing["Published"]
|
||||
kp.timing["DSChange"] = kp.timing["Published"]
|
||||
kp.timing["KRRSIGChange"] = kp.timing["Active"]
|
||||
kp.timing["ZRRSIGChange"] = kp.timing["Active"]
|
||||
|
||||
|
||||
def test_kasp_default(servers):
|
||||
server = servers["ns3"]
|
||||
|
||||
# check the zone with default kasp policy has loaded and is signed.
|
||||
isctest.log.info("check a zone with the default policy is signed")
|
||||
zone = "default.kasp"
|
||||
policy = "default"
|
||||
|
||||
# Key properties.
|
||||
# DNSKEY, RRSIG (ksk), RRSIG (zsk) are published. DS needs to wait.
|
||||
keyprops = [
|
||||
"csk 0 13 256 goal:omnipresent dnskey:rumoured krrsig:rumoured zrrsig:rumoured ds:hidden",
|
||||
]
|
||||
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
|
||||
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
|
||||
isctest.kasp.check_zone_is_signed(server, zone)
|
||||
isctest.kasp.check_keys(zone, keys, expected)
|
||||
set_keytimes_default_policy(expected[0])
|
||||
isctest.kasp.check_keytimes(keys, expected)
|
||||
check_all(server, zone, policy, keys, [])
|
||||
|
||||
# Trigger a keymgr run. Make sure the key files are not touched if there
|
||||
# are no modifications to the key metadata.
|
||||
isctest.log.info(
|
||||
"check that key files are untouched if there are no metadata changes"
|
||||
)
|
||||
key = keys[0]
|
||||
privkey_stat = os.stat(key.privatefile)
|
||||
pubkey_stat = os.stat(key.keyfile)
|
||||
state_stat = os.stat(key.statefile)
|
||||
|
||||
with server.watch_log_from_here() as watcher:
|
||||
server.rndc(f"loadkeys {zone}", log=False)
|
||||
watcher.wait_for_line(f"keymgr: {zone} done")
|
||||
|
||||
assert privkey_stat.st_mtime == os.stat(key.privatefile).st_mtime
|
||||
assert pubkey_stat.st_mtime == os.stat(key.keyfile).st_mtime
|
||||
assert state_stat.st_mtime == os.stat(key.statefile).st_mtime
|
||||
|
||||
# again
|
||||
with server.watch_log_from_here() as watcher:
|
||||
server.rndc(f"loadkeys {zone}", log=False)
|
||||
watcher.wait_for_line(f"keymgr: {zone} done")
|
||||
|
||||
assert privkey_stat.st_mtime == os.stat(key.privatefile).st_mtime
|
||||
assert pubkey_stat.st_mtime == os.stat(key.keyfile).st_mtime
|
||||
assert state_stat.st_mtime == os.stat(key.statefile).st_mtime
|
||||
|
||||
# modify unsigned zone file and check that new record is signed.
|
||||
isctest.log.info("check that an updated zone signs the new record")
|
||||
shutil.copyfile("ns3/template2.db.in", f"ns3/{zone}.db")
|
||||
server.rndc(f"reload {zone}", log=False)
|
||||
|
||||
def update_is_signed():
|
||||
parts = update.split()
|
||||
qname = parts[0]
|
||||
qtype = dns.rdatatype.from_text(parts[1])
|
||||
rdata = parts[2]
|
||||
return isctest.kasp.check_update_is_signed(
|
||||
server, zone, qname, qtype, rdata, keys, []
|
||||
)
|
||||
|
||||
expected_updates = [f"a.{zone}. A 10.0.0.11", f"d.{zone}. A 10.0.0.44"]
|
||||
for update in expected_updates:
|
||||
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
|
||||
|
||||
# Move the private key file, a rekey event should not introduce
|
||||
# replacement keys.
|
||||
isctest.log.info("check that missing private key doesn't trigger rollover")
|
||||
shutil.move(f"{key.privatefile}", f"{key.path}.offline")
|
||||
expectmsg = "zone_rekey:zone_verifykeys failed: some key files are missing"
|
||||
with server.watch_log_from_here() as watcher:
|
||||
server.rndc(f"loadkeys {zone}", log=False)
|
||||
watcher.wait_for_line(f"zone {zone}/IN (signed): {expectmsg}")
|
||||
# Nothing has changed.
|
||||
expected[0].properties["private"] = False
|
||||
isctest.kasp.check_keys(zone, keys, expected)
|
||||
isctest.kasp.check_keytimes(keys, expected)
|
||||
check_all(server, zone, policy, keys, [])
|
||||
|
||||
# A zone that uses inline-signing.
|
||||
isctest.log.info("check an inline-signed zone with the default policy is signed")
|
||||
zone = "inline-signing.kasp"
|
||||
# Key properties.
|
||||
key1 = KeyProperties.default()
|
||||
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
|
||||
expected = [key1]
|
||||
isctest.kasp.check_zone_is_signed(server, zone)
|
||||
isctest.kasp.check_keys(zone, keys, expected)
|
||||
set_keytimes_default_policy(key1)
|
||||
isctest.kasp.check_keytimes(keys, expected)
|
||||
check_all(server, zone, policy, keys, [])
|
||||
|
||||
|
||||
def test_kasp_dynamic(servers):
|
||||
# Dynamic update test cases.
|
||||
server = servers["ns3"]
|
||||
|
||||
# Standard dynamic zone.
|
||||
isctest.log.info("check dynamic zone is updated and signed after update")
|
||||
zone = "dynamic.kasp"
|
||||
policy = "default"
|
||||
# Key properties.
|
||||
key1 = KeyProperties.default()
|
||||
expected = [key1]
|
||||
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
|
||||
isctest.kasp.check_zone_is_signed(server, zone)
|
||||
isctest.kasp.check_keys(zone, keys, expected)
|
||||
set_keytimes_default_policy(key1)
|
||||
expected = [key1]
|
||||
isctest.kasp.check_keytimes(keys, expected)
|
||||
check_all(server, zone, policy, keys, [])
|
||||
|
||||
# Update zone with nsupdate.
|
||||
def nsupdate():
|
||||
message = dns.update.UpdateMessage(zone)
|
||||
for update in updates:
|
||||
if update[0] == 0:
|
||||
message.delete(update[1], update[2], update[3])
|
||||
else:
|
||||
message.add(update[1], update[2], update[3], update[4])
|
||||
|
||||
try:
|
||||
response = isctest.query.udp(
|
||||
message, server.ip, server.ports.dns, timeout=3
|
||||
)
|
||||
assert response.rcode() == dns.rcode.NOERROR
|
||||
except dns.exception.Timeout:
|
||||
isctest.log.info(f"error: update timeout for {zone}")
|
||||
|
||||
isctest.log.debug(f"update of zone {zone} to server {server.ip} successful")
|
||||
|
||||
def update_is_signed():
|
||||
parts = update.split()
|
||||
qname = parts[0]
|
||||
qtype = dns.rdatatype.from_text(parts[1])
|
||||
rdata = parts[2]
|
||||
return isctest.kasp.check_update_is_signed(
|
||||
server, zone, qname, qtype, rdata, keys, []
|
||||
)
|
||||
|
||||
updates = [
|
||||
[0, f"a.{zone}.", "A", "10.0.0.1"],
|
||||
[1, f"a.{zone}.", 300, "A", "10.0.0.101"],
|
||||
[1, f"d.{zone}.", 300, "A", "10.0.0.4"],
|
||||
]
|
||||
nsupdate()
|
||||
|
||||
expected_updates = [f"a.{zone}. A 10.0.0.101", f"d.{zone}. A 10.0.0.4"]
|
||||
for update in expected_updates:
|
||||
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
|
||||
|
||||
# Update zone with nsupdate (reverting the above change).
|
||||
updates = [
|
||||
[1, f"a.{zone}.", 300, "A", "10.0.0.1"],
|
||||
[0, f"a.{zone}.", "A", "10.0.0.101"],
|
||||
[0, f"d.{zone}.", "A", "10.0.0.4"],
|
||||
]
|
||||
nsupdate()
|
||||
|
||||
update = f"a.{zone}. A 10.0.0.1"
|
||||
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
|
||||
|
||||
# Update zone with freeze/thaw.
|
||||
isctest.log.info("check dynamic zone is updated and signed after freeze and thaw")
|
||||
with server.watch_log_from_here() as watcher:
|
||||
server.rndc(f"freeze {zone}", log=False)
|
||||
watcher.wait_for_line(f"freezing zone '{zone}/IN': success")
|
||||
|
||||
time.sleep(1)
|
||||
with open(f"ns3/{zone}.db", "a", encoding="utf-8") as zonefile:
|
||||
zonefile.write(f"d.{zone}. 300 A 10.0.0.44\n")
|
||||
time.sleep(1)
|
||||
|
||||
with server.watch_log_from_here() as watcher:
|
||||
server.rndc(f"thaw {zone}", log=False)
|
||||
watcher.wait_for_line(f"thawing zone '{zone}/IN': success")
|
||||
|
||||
expected_updates = [f"a.{zone}. A 10.0.0.1", f"d.{zone}. A 10.0.0.44"]
|
||||
|
||||
for update in expected_updates:
|
||||
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
|
||||
|
||||
# Dynamic, and inline-signing.
|
||||
zone = "dynamic-inline-signing.kasp"
|
||||
# Key properties.
|
||||
key1 = KeyProperties.default()
|
||||
expected = [key1]
|
||||
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
|
||||
isctest.kasp.check_zone_is_signed(server, zone)
|
||||
isctest.kasp.check_keys(zone, keys, expected)
|
||||
set_keytimes_default_policy(key1)
|
||||
expected = [key1]
|
||||
isctest.kasp.check_keytimes(keys, expected)
|
||||
check_all(server, zone, policy, keys, [])
|
||||
|
||||
# Update zone with freeze/thaw.
|
||||
isctest.log.info(
|
||||
"check dynamic inline-signed zone is updated and signed after freeze and thaw"
|
||||
)
|
||||
with server.watch_log_from_here() as watcher:
|
||||
server.rndc(f"freeze {zone}", log=False)
|
||||
watcher.wait_for_line(f"freezing zone '{zone}/IN': success")
|
||||
|
||||
time.sleep(1)
|
||||
shutil.copyfile("ns3/template2.db.in", f"ns3/{zone}.db")
|
||||
time.sleep(1)
|
||||
|
||||
with server.watch_log_from_here() as watcher:
|
||||
server.rndc(f"thaw {zone}", log=False)
|
||||
watcher.wait_for_line(f"thawing zone '{zone}/IN': success")
|
||||
|
||||
expected_updates = [f"a.{zone}. A 10.0.0.11", f"d.{zone}. A 10.0.0.44"]
|
||||
for update in expected_updates:
|
||||
isctest.run.retry_with_timeout(update_is_signed, timeout=5)
|
||||
|
||||
# Dynamic, signed, and inline-signing.
|
||||
isctest.log.info("check dynamic signed, and inline-signed zone")
|
||||
zone = "dynamic-signed-inline-signing.kasp"
|
||||
# Key properties.
|
||||
key1 = KeyProperties.default()
|
||||
# The ns3/setup.sh script sets all states to omnipresent.
|
||||
key1.metadata["DNSKEYState"] = "omnipresent"
|
||||
key1.metadata["KRRSIGState"] = "omnipresent"
|
||||
key1.metadata["ZRRSIGState"] = "omnipresent"
|
||||
key1.metadata["DSState"] = "omnipresent"
|
||||
expected = [key1]
|
||||
keys = isctest.kasp.keydir_to_keylist(zone, "ns3/keys")
|
||||
isctest.kasp.check_zone_is_signed(server, zone)
|
||||
isctest.kasp.check_keys(zone, keys, expected)
|
||||
check_all(server, zone, policy, keys, [])
|
||||
# Ensure no zone_resigninc for the unsigned version of the zone is triggered.
|
||||
assert f"zone_resigninc: zone {zone}/IN (unsigned): enter" not in "ns3/named.run"
|
||||
|
||||
|
||||
def test_kasp_special_cases(servers):
|
||||
server = servers["ns3"]
|
||||
|
||||
# Insecure zones.
|
||||
isctest.log.info("check insecure zones")
|
||||
|
||||
zone = "insecure.kasp"
|
||||
expected = []
|
||||
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
|
||||
isctest.kasp.check_keys(zone, keys, expected)
|
||||
isctest.kasp.check_dnssecstatus(server, zone, keys, policy="insecure")
|
||||
isctest.kasp.check_apex(server, zone, keys, [])
|
||||
isctest.kasp.check_subdomain(server, zone, keys, [])
|
||||
|
||||
zone = "unsigned.kasp"
|
||||
expected = []
|
||||
keys = isctest.kasp.keydir_to_keylist(zone, "ns3")
|
||||
isctest.kasp.check_keys(zone, keys, expected)
|
||||
isctest.kasp.check_dnssecstatus(server, zone, keys, policy=None)
|
||||
isctest.kasp.check_apex(server, zone, keys, [])
|
||||
isctest.kasp.check_subdomain(server, zone, keys, [])
|
||||
# Make sure the zone file is untouched.
|
||||
isctest.check.file_contents_equal(f"ns3/{zone}.db.infile", f"ns3/{zone}.db")
|
||||
|
||||
# A zone with special characters.
|
||||
isctest.log.info("check special characters")
|
||||
|
||||
zone = r'i-am.":\;?&[]\@!\$*+,|=\.\(\)special.kasp'
|
||||
# It is non-trivial to adapt the tests to deal with all possible different
|
||||
# escaping characters, so we will just try to verify the zone.
|
||||
isctest.kasp.check_dnssec_verify(server, zone)
|
||||
|
||||
# check that max-zone-ttl rejects zones with too high TTL.
|
||||
isctest.log.info("check max-zone-ttl rejects zones with too high TTL")
|
||||
zone = "max-zone-ttl.kasp"
|
||||
assert f"loading from master file {zone}.db failed: out of range" in server.log
|
||||
|
||||
|
||||
def test_kasp_dnssec_keygen():
|
||||
def keygen(zone, policy, keydir=None):
|
||||
if keydir is None:
|
||||
keydir = "."
|
||||
|
||||
keygen_command = [
|
||||
os.environ.get("KEYGEN"),
|
||||
"-K",
|
||||
keydir,
|
||||
"-k",
|
||||
policy,
|
||||
"-l",
|
||||
"kasp.conf",
|
||||
zone,
|
||||
]
|
||||
|
||||
return isctest.run.cmd(keygen_command, log_stdout=True).stdout.decode("utf-8")
|
||||
|
||||
# check that 'dnssec-keygen -k' (configured policy) creates valid files.
|
||||
lifetime = {
|
||||
"P1Y": int(timedelta(days=365).total_seconds()),
|
||||
"P30D": int(timedelta(days=30).total_seconds()),
|
||||
"P6M": int(timedelta(days=31 * 6).total_seconds()),
|
||||
}
|
||||
keyprops = [
|
||||
f"csk {lifetime['P1Y']} 13 256",
|
||||
f"ksk {lifetime['P1Y']} 8 2048",
|
||||
f"zsk {lifetime['P30D']} 8 2048",
|
||||
f"zsk {lifetime['P6M']} 8 3072",
|
||||
]
|
||||
keydir = "keys"
|
||||
out = keygen("kasp", "kasp", keydir)
|
||||
keys = isctest.kasp.keystr_to_keylist(out, keydir)
|
||||
expected = isctest.kasp.policy_to_properties(ttl=200, keys=keyprops)
|
||||
isctest.kasp.check_keys("kasp", keys, expected)
|
||||
|
||||
# check that 'dnssec-keygen -k' (default policy) creates valid files.
|
||||
keyprops = ["csk 0 13 256"]
|
||||
out = keygen("kasp", "default")
|
||||
keys = isctest.kasp.keystr_to_keylist(out)
|
||||
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
|
||||
isctest.kasp.check_keys("kasp", keys, expected)
|
||||
|
||||
# check that 'dnssec-settime' by default does not edit key state file.
|
||||
key = keys[0]
|
||||
privatefile = f"{key.path}.private"
|
||||
keyfile = f"{key.path}.key"
|
||||
statefile = f"{key.path}.state"
|
||||
shutil.copyfile(privatefile, f"{privatefile}.backup")
|
||||
shutil.copyfile(keyfile, f"{keyfile}.backup")
|
||||
shutil.copyfile(statefile, f"{statefile}.backup")
|
||||
|
||||
created = key.get_timing("Created")
|
||||
publish = key.get_timing("Publish") + timedelta(hours=1)
|
||||
settime = [
|
||||
os.environ.get("SETTIME"),
|
||||
"-P",
|
||||
str(publish),
|
||||
key.path,
|
||||
]
|
||||
out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
|
||||
|
||||
isctest.check.file_contents_equal(f"{key.path}.state", f"{key.path}.state.backup")
|
||||
assert key.get_metadata("Publish", file=key.privatefile) == str(publish)
|
||||
assert key.get_metadata("Publish", file=key.keyfile, comment=True) == str(publish)
|
||||
|
||||
# check that 'dnssec-settime -s' also sets publish time metadata and
|
||||
# states in key state file.
|
||||
now = KeyTimingMetadata.now()
|
||||
goal = "omnipresent"
|
||||
dnskey = "rumoured"
|
||||
krrsig = "rumoured"
|
||||
zrrsig = "omnipresent"
|
||||
ds = "hidden"
|
||||
keyprops = [
|
||||
f"csk 0 13 256 goal:{goal} dnskey:{dnskey} krrsig:{krrsig} zrrsig:{zrrsig} ds:{ds}",
|
||||
]
|
||||
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
|
||||
expected[0].timing = {
|
||||
"Generated": created,
|
||||
"Published": now,
|
||||
"Active": created,
|
||||
"DNSKEYChange": now,
|
||||
"KRRSIGChange": now,
|
||||
"ZRRSIGChange": now,
|
||||
"DSChange": now,
|
||||
}
|
||||
|
||||
settime = [
|
||||
os.environ.get("SETTIME"),
|
||||
"-s",
|
||||
"-P",
|
||||
str(now),
|
||||
"-g",
|
||||
goal,
|
||||
"-k",
|
||||
dnskey,
|
||||
str(now),
|
||||
"-r",
|
||||
krrsig,
|
||||
str(now),
|
||||
"-z",
|
||||
zrrsig,
|
||||
str(now),
|
||||
"-d",
|
||||
ds,
|
||||
str(now),
|
||||
key.path,
|
||||
]
|
||||
out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
|
||||
isctest.kasp.check_keys("kasp", keys, expected)
|
||||
isctest.kasp.check_keytimes(keys, expected)
|
||||
|
||||
# check that 'dnssec-settime -s' also unsets publish time metadata and
|
||||
# states in key state file.
|
||||
now = KeyTimingMetadata.now()
|
||||
keyprops = ["csk 0 13 256"]
|
||||
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
|
||||
expected[0].timing = {
|
||||
"Generated": created,
|
||||
"Active": created,
|
||||
}
|
||||
|
||||
settime = [
|
||||
os.environ.get("SETTIME"),
|
||||
"-s",
|
||||
"-P",
|
||||
"none",
|
||||
"-g",
|
||||
"none",
|
||||
"-k",
|
||||
"none",
|
||||
str(now),
|
||||
"-z",
|
||||
"none",
|
||||
str(now),
|
||||
"-r",
|
||||
"none",
|
||||
str(now),
|
||||
"-d",
|
||||
"none",
|
||||
str(now),
|
||||
key.path,
|
||||
]
|
||||
out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
|
||||
isctest.kasp.check_keys("kasp", keys, expected)
|
||||
isctest.kasp.check_keytimes(keys, expected)
|
||||
|
||||
# check that 'dnssec-settime -s' also sets active time metadata and states in key state file (uppercase)
|
||||
soon = now + timedelta(hours=2)
|
||||
goal = "hidden"
|
||||
dnskey = "unretentive"
|
||||
krrsig = "omnipresent"
|
||||
zrrsig = "unretentive"
|
||||
ds = "omnipresent"
|
||||
keyprops = [
|
||||
f"csk 0 13 256 goal:{goal} dnskey:{dnskey} krrsig:{krrsig} zrrsig:{zrrsig} ds:{ds}",
|
||||
]
|
||||
expected = isctest.kasp.policy_to_properties(ttl=3600, keys=keyprops)
|
||||
expected[0].timing = {
|
||||
"Generated": created,
|
||||
"Active": soon,
|
||||
"DNSKEYChange": soon,
|
||||
"KRRSIGChange": soon,
|
||||
"ZRRSIGChange": soon,
|
||||
"DSChange": soon,
|
||||
}
|
||||
|
||||
settime = [
|
||||
os.environ.get("SETTIME"),
|
||||
"-s",
|
||||
"-A",
|
||||
str(soon),
|
||||
"-g",
|
||||
"HIDDEN",
|
||||
"-k",
|
||||
"UNRETENTIVE",
|
||||
str(soon),
|
||||
"-z",
|
||||
"UNRETENTIVE",
|
||||
str(soon),
|
||||
"-r",
|
||||
"OMNIPRESENT",
|
||||
str(soon),
|
||||
"-d",
|
||||
"OMNIPRESENT",
|
||||
str(soon),
|
||||
key.path,
|
||||
]
|
||||
out = isctest.run.cmd(settime, log_stdout=True).stdout.decode("utf-8")
|
||||
isctest.kasp.check_keys("kasp", keys, expected)
|
||||
isctest.kasp.check_keytimes(keys, expected)
|
||||
@@ -10,14 +10,19 @@
|
||||
# information regarding copyright ownership.
|
||||
|
||||
from datetime import timedelta
|
||||
import difflib
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
from typing import List, Optional
|
||||
|
||||
import pytest
|
||||
|
||||
import isctest
|
||||
from isctest.kasp import KeyTimingMetadata
|
||||
from isctest.kasp import (
|
||||
Key,
|
||||
KeyTimingMetadata,
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.extra_artifacts(
|
||||
[
|
||||
@@ -84,6 +89,31 @@ def between(value, start, end):
|
||||
return start < value < end
|
||||
|
||||
|
||||
def check_file_contents_equal(file1, file2):
|
||||
def normalize_line(line):
|
||||
# remove trailing&leading whitespace and replace multiple whitespaces
|
||||
return " ".join(line.split())
|
||||
|
||||
def read_lines(file_path):
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
return [normalize_line(line) for line in file.readlines()]
|
||||
|
||||
lines1 = read_lines(file1)
|
||||
lines2 = read_lines(file2)
|
||||
|
||||
differ = difflib.Differ()
|
||||
diff = differ.compare(lines1, lines2)
|
||||
|
||||
for line in diff:
|
||||
assert not line.startswith("+ ") and not line.startswith(
|
||||
"- "
|
||||
), f'file contents of "{file1}" and "{file2}" differ'
|
||||
|
||||
|
||||
def keystr_to_keylist(keystr: str, keydir: Optional[str] = None) -> List[Key]:
|
||||
return [Key(name, keydir) for name in keystr.split()]
|
||||
|
||||
|
||||
def ksr(zone, policy, action, options="", raise_on_exception=True):
|
||||
ksr_command = [
|
||||
os.environ.get("KSR"),
|
||||
@@ -485,14 +515,14 @@ def test_ksr_common(servers):
|
||||
# create ksk
|
||||
kskdir = "ns1/offline"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +1y -o")
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 1
|
||||
|
||||
check_keys(ksks, None)
|
||||
|
||||
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
|
||||
out, _ = ksr(zone, policy, "keygen", options="-i now -e +1y")
|
||||
zsks = isctest.kasp.keystr_to_keylist(out)
|
||||
zsks = keystr_to_keylist(out)
|
||||
assert len(zsks) == 2
|
||||
|
||||
lifetime = timedelta(days=31 * 6)
|
||||
@@ -502,7 +532,7 @@ def test_ksr_common(servers):
|
||||
# in the given key directory
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +1y")
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
assert len(zsks) == 2
|
||||
|
||||
lifetime = timedelta(days=31 * 6)
|
||||
@@ -545,22 +575,18 @@ def test_ksr_common(servers):
|
||||
# check that 'dnssec-ksr keygen' selects pregenerated keys for
|
||||
# the same time bundle
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i {now} -e +1y")
|
||||
selected_zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
selected_zsks = keystr_to_keylist(out, zskdir)
|
||||
assert len(selected_zsks) == 2
|
||||
for index, key in enumerate(selected_zsks):
|
||||
assert zsks[index] == key
|
||||
isctest.check.file_contents_equal(
|
||||
f"{key.path}.private", f"{key.path}.private.backup"
|
||||
)
|
||||
isctest.check.file_contents_equal(f"{key.path}.key", f"{key.path}.key.backup")
|
||||
isctest.check.file_contents_equal(
|
||||
f"{key.path}.state", f"{key.path}.state.backup"
|
||||
)
|
||||
check_file_contents_equal(f"{key.path}.private", f"{key.path}.private.backup")
|
||||
check_file_contents_equal(f"{key.path}.key", f"{key.path}.key.backup")
|
||||
check_file_contents_equal(f"{key.path}.state", f"{key.path}.state.backup")
|
||||
|
||||
# check that 'dnssec-ksr keygen' generates only necessary keys for
|
||||
# overlapping time bundle
|
||||
out, err = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i {now} -e +2y -v 1")
|
||||
overlapping_zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
overlapping_zsks = keystr_to_keylist(out, zskdir)
|
||||
assert len(overlapping_zsks) == 4
|
||||
|
||||
verbose = err.split()
|
||||
@@ -571,24 +597,24 @@ def test_ksr_common(servers):
|
||||
selected += 1
|
||||
if "Generating" in output:
|
||||
generated += 1
|
||||
# Subtract if there was a key collision.
|
||||
if "collide" in output:
|
||||
generated -= 1
|
||||
|
||||
assert selected == 2
|
||||
assert generated == 2
|
||||
for index, key in enumerate(overlapping_zsks):
|
||||
if index < 2:
|
||||
assert zsks[index] == key
|
||||
isctest.check.file_contents_equal(
|
||||
check_file_contents_equal(
|
||||
f"{key.path}.private", f"{key.path}.private.backup"
|
||||
)
|
||||
isctest.check.file_contents_equal(
|
||||
f"{key.path}.key", f"{key.path}.key.backup"
|
||||
)
|
||||
isctest.check.file_contents_equal(
|
||||
f"{key.path}.state", f"{key.path}.state.backup"
|
||||
)
|
||||
check_file_contents_equal(f"{key.path}.key", f"{key.path}.key.backup")
|
||||
check_file_contents_equal(f"{key.path}.state", f"{key.path}.state.backup")
|
||||
|
||||
# run 'dnssec-ksr keygen' again with verbosity 0
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i {now} -e +2y")
|
||||
overlapping_zsks2 = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
overlapping_zsks2 = keystr_to_keylist(out, zskdir)
|
||||
assert len(overlapping_zsks2) == 4
|
||||
check_keys(overlapping_zsks2, lifetime)
|
||||
for index, key in enumerate(overlapping_zsks2):
|
||||
@@ -683,7 +709,7 @@ def test_ksr_lastbundle(servers):
|
||||
kskdir = "ns1/offline"
|
||||
offset = -timedelta(days=365)
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i -1y -e +1d -o")
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 1
|
||||
|
||||
check_keys(ksks, None, offset=offset)
|
||||
@@ -691,7 +717,7 @@ def test_ksr_lastbundle(servers):
|
||||
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i -1y -e +1d")
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
assert len(zsks) == 2
|
||||
|
||||
lifetime = timedelta(days=31 * 6)
|
||||
@@ -762,7 +788,7 @@ def test_ksr_inthemiddle(servers):
|
||||
kskdir = "ns1/offline"
|
||||
offset = -timedelta(days=365)
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i -1y -e +1y -o")
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 1
|
||||
|
||||
check_keys(ksks, None, offset=offset)
|
||||
@@ -770,7 +796,7 @@ def test_ksr_inthemiddle(servers):
|
||||
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i -1y -e +1y")
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
assert len(zsks) == 4
|
||||
|
||||
lifetime = timedelta(days=31 * 6)
|
||||
@@ -842,13 +868,13 @@ def check_ksr_rekey_logs_error(server, zone, policy, offset, end):
|
||||
then = now + offset
|
||||
until = now + end
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i {then} -e {until} -o")
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 1
|
||||
|
||||
# key generation
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i {then} -e {until}")
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
assert len(zsks) == 2
|
||||
|
||||
# create request
|
||||
@@ -915,7 +941,7 @@ def test_ksr_unlimited(servers):
|
||||
# create ksk
|
||||
kskdir = "ns1/offline"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +2y -o")
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 1
|
||||
|
||||
check_keys(ksks, None)
|
||||
@@ -923,7 +949,7 @@ def test_ksr_unlimited(servers):
|
||||
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +2y")
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
assert len(zsks) == 1
|
||||
|
||||
lifetime = None
|
||||
@@ -1032,7 +1058,7 @@ def test_ksr_twotone(servers):
|
||||
# create ksk
|
||||
kskdir = "ns1/offline"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +1y -o")
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 2
|
||||
|
||||
ksks_defalg = []
|
||||
@@ -1056,7 +1082,7 @@ def test_ksr_twotone(servers):
|
||||
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +1y")
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
# First algorithm keys have a lifetime of 3 months, so there should
|
||||
# be 4 created keys. Second algorithm keys have a lifetime of 5
|
||||
# months, so there should be 3 created keys. While only two time
|
||||
@@ -1150,7 +1176,7 @@ def test_ksr_kskroll(servers):
|
||||
# create ksk
|
||||
kskdir = "ns1/offline"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {kskdir} -i now -e +1y -o")
|
||||
ksks = isctest.kasp.keystr_to_keylist(out, kskdir)
|
||||
ksks = keystr_to_keylist(out, kskdir)
|
||||
assert len(ksks) == 2
|
||||
|
||||
lifetime = timedelta(days=31 * 6)
|
||||
@@ -1159,7 +1185,7 @@ def test_ksr_kskroll(servers):
|
||||
# check that 'dnssec-ksr keygen' pregenerates right amount of keys
|
||||
zskdir = "ns1"
|
||||
out, _ = ksr(zone, policy, "keygen", options=f"-K {zskdir} -i now -e +1y")
|
||||
zsks = isctest.kasp.keystr_to_keylist(out, zskdir)
|
||||
zsks = keystr_to_keylist(out, zskdir)
|
||||
assert len(zsks) == 1
|
||||
|
||||
check_keys(zsks, None)
|
||||
|
||||
@@ -385,7 +385,7 @@ $DIG $DIGOPTS @10.53.0.3 foo.initially-unavailable. A >dig.out.ns3.test$n.1 2>&1
|
||||
grep "NOERROR" dig.out.ns3.test$n.1 >/dev/null || ret=1
|
||||
grep "flags:.* ad" dig.out.ns3.test$n.1 >/dev/null || ret=1
|
||||
# Sanity check: the authoritative server should have been queried.
|
||||
nextpart ns2/named.run | grep "query 'foo.initially-unavailable/A/IN'" >/dev/null || ret=1
|
||||
nextpart ns2/named.run | grep "query 'foo.initially-unavailable/NS/IN'" >/dev/null || ret=1
|
||||
# Reconfigure ns2 so that the zone can be mirrored on ns3.
|
||||
sed '/^zone "initially-unavailable" {$/,/^};$/ {
|
||||
s/10.53.0.254/10.53.0.3/
|
||||
@@ -403,7 +403,7 @@ $DIG $DIGOPTS @10.53.0.3 foo.initially-unavailable. A >dig.out.ns3.test$n.2 2>&1
|
||||
grep "NOERROR" dig.out.ns3.test$n.2 >/dev/null || ret=1
|
||||
grep "flags:.* ad" dig.out.ns3.test$n.2 >/dev/null || ret=1
|
||||
# Ensure the authoritative server was not queried.
|
||||
nextpart ns2/named.run | grep "query 'foo.initially-unavailable/A/IN'" >/dev/null && ret=1
|
||||
nextpart ns2/named.run | grep "query 'foo.initially-unavailable/NS/IN'" >/dev/null && ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
@@ -434,7 +434,7 @@ $DIG $DIGOPTS @10.53.0.3 foo.initially-unavailable. A >dig.out.ns3.test$n 2>&1 |
|
||||
grep "NOERROR" dig.out.ns3.test$n >/dev/null || ret=1
|
||||
grep "flags:.* ad" dig.out.ns3.test$n >/dev/null || ret=1
|
||||
# Sanity check: the authoritative server should have been queried.
|
||||
nextpart ns2/named.run | grep "query 'foo.initially-unavailable/A/IN'" >/dev/null || ret=1
|
||||
nextpart ns2/named.run | grep "query 'foo.initially-unavailable/NS/IN'" >/dev/null || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
|
||||
@@ -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
-330
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
@@ -127,12 +127,14 @@ ADDR a.bit.longer.ns.name.good.
|
||||
ADDR ns2.good.
|
||||
ADDR ns3.good.
|
||||
ADDR ns3.good.
|
||||
NS a.bit.longer.ns.name.good.
|
||||
NS bit.longer.ns.name.good.
|
||||
NS boing.good.
|
||||
NS good.
|
||||
NS longer.ns.name.good.
|
||||
NS name.good.
|
||||
NS ns.name.good.
|
||||
NS ns3.good.
|
||||
NS zoop.boing.good.
|
||||
__EOF
|
||||
cat <<__EOF | diff ans3/query.log - >/dev/null || ret=1
|
||||
@@ -165,11 +167,13 @@ ADDR a.bit.longer.ns.name.good.
|
||||
ADDR ns2.good.
|
||||
ADDR ns3.good.
|
||||
ADDR ns3.good.
|
||||
NS a.bit.longer.ns.name.good.
|
||||
NS bit.longer.ns.name.good.
|
||||
NS boing.good.
|
||||
NS longer.ns.name.good.
|
||||
NS name.good.
|
||||
NS ns.name.good.
|
||||
NS ns3.good.
|
||||
NS zoop.boing.good.
|
||||
__EOF
|
||||
cat <<__EOF | diff ans3/query.log - >/dev/null || ret=1
|
||||
@@ -221,6 +225,7 @@ ADDR ns3.bad.
|
||||
ADDR ns3.bad.
|
||||
NS boing.bad.
|
||||
NS name.bad.
|
||||
NS ns3.bad.
|
||||
__EOF
|
||||
cat <<__EOF | diff ans3/query.log - >/dev/null || ret=1
|
||||
ADDR icky.icky.icky.ptang.zoop.boing.bad.
|
||||
@@ -271,6 +276,7 @@ ADDR ns3.ugly.
|
||||
NS boing.ugly.
|
||||
NS name.ugly.
|
||||
NS name.ugly.
|
||||
NS ns3.ugly.
|
||||
__EOF
|
||||
echo "ADDR icky.icky.icky.ptang.zoop.boing.ugly." | diff ans3/query.log - >/dev/null || ret=1
|
||||
echo "ADDR icky.icky.icky.ptang.zoop.boing.ugly." | diff ans4/query.log - >/dev/null || ret=1
|
||||
@@ -302,11 +308,13 @@ ADDR a.bit.longer.ns.name.slow.
|
||||
ADDR ns2.slow.
|
||||
ADDR ns3.slow.
|
||||
ADDR ns3.slow.
|
||||
NS a.bit.longer.ns.name.slow.
|
||||
NS bit.longer.ns.name.slow.
|
||||
NS boing.slow.
|
||||
NS longer.ns.name.slow.
|
||||
NS name.slow.
|
||||
NS ns.name.slow.
|
||||
NS ns3.slow.
|
||||
NS slow.
|
||||
NS zoop.boing.slow.
|
||||
__EOF
|
||||
@@ -340,6 +348,7 @@ NS 8.f.4.0.1.0.0.2.ip6.arpa.
|
||||
NS 0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.
|
||||
NS 0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.
|
||||
NS 0.0.0.0.0.0.0.0.8.f.4.0.1.0.0.2.ip6.arpa.
|
||||
NS 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.
|
||||
PTR 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.
|
||||
__EOF
|
||||
for ans in ans2 ans3 ans4; do mv -f $ans/query.log query-$ans-$n.log 2>/dev/null || true; done
|
||||
@@ -362,12 +371,14 @@ ADDR a.bit.longer.ns.name.good.
|
||||
ADDR ns2.good.
|
||||
ADDR ns3.good.
|
||||
ADDR ns3.good.
|
||||
NS a.bit.longer.ns.name.good.
|
||||
NS bit.longer.ns.name.good.
|
||||
NS boing.good.
|
||||
NS good.
|
||||
NS longer.ns.name.good.
|
||||
NS name.good.
|
||||
NS ns.name.good.
|
||||
NS ns3.good.
|
||||
NS zoop.boing.good.
|
||||
__EOF
|
||||
cat <<__EOF | diff ans3/query.log - >/dev/null || ret=1
|
||||
@@ -449,6 +460,7 @@ grep "a\.b\.stale\..*1.*IN.*TXT.*hooray" dig.out.test$n >/dev/null || ret=1
|
||||
sleep 1
|
||||
sort ans2/query.log >ans2/query.log.sorted
|
||||
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
|
||||
ADDR ns.a.b.stale.
|
||||
ADDR ns.b.stale.
|
||||
ADDR ns2.stale.
|
||||
NS b.stale.
|
||||
@@ -457,7 +469,9 @@ __EOF
|
||||
test -f ans3/query.log && ret=1
|
||||
sort ans4/query.log >ans4/query.log.sorted
|
||||
cat <<__EOF | diff ans4/query.log.sorted - >/dev/null || ret=1
|
||||
ADDR ns.a.b.stale.
|
||||
ADDR ns.b.stale.
|
||||
NS a.b.stale.
|
||||
NS b.stale.
|
||||
TXT a.b.stale.
|
||||
__EOF
|
||||
@@ -476,6 +490,7 @@ grep "a\.b\.stale\..*1.*IN.*TXT.*hooray" dig.out.test$n >/dev/null || ret=1
|
||||
sleep 1
|
||||
sort ans2/query.log >ans2/query.log.sorted
|
||||
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
|
||||
ADDR ns.a.b.stale.
|
||||
ADDR ns.b.stale.
|
||||
ADDR ns2.stale.
|
||||
NS b.stale.
|
||||
@@ -483,7 +498,9 @@ __EOF
|
||||
test -f ans3/query.log && ret=1
|
||||
sort ans4/query.log >ans4/query.log.sorted
|
||||
cat <<__EOF | diff ans4/query.log.sorted - >/dev/null || ret=1
|
||||
ADDR ns.a.b.stale.
|
||||
ADDR ns.b.stale.
|
||||
NS a.b.stale.
|
||||
TXT a.b.stale.
|
||||
__EOF
|
||||
for ans in ans2 ans3 ans4; do mv -f $ans/query.log query-$ans-$n.log 2>/dev/null || true; done
|
||||
@@ -519,6 +536,7 @@ grep "a\.b\.stale\..*1.*IN.*TXT.*hooray" dig.out.test$n >/dev/null || ret=1
|
||||
sleep 1
|
||||
sort ans2/query.log >ans2/query.log.sorted
|
||||
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
|
||||
ADDR ns.a.b.stale.
|
||||
ADDR ns.b.stale.
|
||||
ADDR ns2.stale.
|
||||
NS b.stale.
|
||||
@@ -527,7 +545,9 @@ __EOF
|
||||
test -f ans3/query.log && ret=1
|
||||
sort ans4/query.log >ans4/query.log.sorted
|
||||
cat <<__EOF | diff ans4/query.log.sorted - >/dev/null || ret=1
|
||||
ADDR ns.a.b.stale.
|
||||
ADDR ns.b.stale.
|
||||
NS a.b.stale.
|
||||
NS b.stale.
|
||||
TXT a.b.stale.
|
||||
__EOF
|
||||
@@ -546,6 +566,7 @@ grep "a\.b\.stale\..*1.*IN.*TXT.*hooray" dig.out.test$n >/dev/null || ret=1
|
||||
sleep 1
|
||||
sort ans2/query.log >ans2/query.log.sorted
|
||||
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
|
||||
ADDR ns.a.b.stale.
|
||||
ADDR ns.b.stale.
|
||||
ADDR ns2.stale.
|
||||
NS b.stale.
|
||||
@@ -553,7 +574,9 @@ __EOF
|
||||
test -f ans3/query.log && ret=1
|
||||
sort ans4/query.log >ans4/query.log.sorted
|
||||
cat <<__EOF | diff ans4/query.log.sorted - >/dev/null || ret=1
|
||||
ADDR ns.a.b.stale.
|
||||
ADDR ns.b.stale.
|
||||
NS a.b.stale.
|
||||
TXT a.b.stale.
|
||||
__EOF
|
||||
for ans in ans2 ans3 ans4; do mv -f $ans/query.log query-$ans-$n.log 2>/dev/null || true; done
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
; See the COPYRIGHT file distributed with this work for additional
|
||||
; information regarding copyright ownership.
|
||||
|
||||
$TTL 60
|
||||
$TTL 120
|
||||
|
||||
big. IN SOA ns.big. hostmaster.ns.big. 1 0 0 0 60
|
||||
big. IN SOA ns.big. hostmaster.ns.big. 1 0 0 0 120
|
||||
big. IN NS ns.big.
|
||||
ns.big. IN A 10.53.0.1
|
||||
|
||||
|
||||
@@ -280,11 +280,11 @@ echo_i "checking that priority names under the max-types-per-name limit get cach
|
||||
|
||||
# Query for NXDOMAIN for items on our priority list - these should get cached
|
||||
for rrtype in AAAA MX NS; do
|
||||
check_manytypes 1 manytypes.big "${rrtype}" NOERROR big SOA 60 || ret=1
|
||||
check_manytypes 1 manytypes.big "${rrtype}" NOERROR big SOA 120 || ret=1
|
||||
done
|
||||
# Wait at least 1 second
|
||||
for rrtype in AAAA MX NS; do
|
||||
check_manytypes 2 manytypes.big "${rrtype}" NOERROR big SOA "" 60 || ret=1
|
||||
check_manytypes 2 manytypes.big "${rrtype}" NOERROR big SOA "" 120 || ret=1
|
||||
done
|
||||
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
@@ -299,13 +299,13 @@ echo_i "checking that NXDOMAIN names under the max-types-per-name limit get cach
|
||||
|
||||
# Query for 10 NXDOMAIN types
|
||||
for ntype in $(seq 65270 65279); do
|
||||
check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR big SOA 60 || ret=1
|
||||
check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR big SOA 120 || ret=1
|
||||
done
|
||||
# Wait at least 1 second
|
||||
sleep 1
|
||||
# Query for 10 NXDOMAIN types again - these should be cached
|
||||
for ntype in $(seq 65270 65279); do
|
||||
check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR big SOA "" 60 || ret=1
|
||||
check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR big SOA "" 120 || ret=1
|
||||
done
|
||||
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
@@ -318,13 +318,13 @@ echo_i "checking that existing names under the max-types-per-name limit get cach
|
||||
|
||||
# Limited to 10 types - these should be cached and the previous record should be evicted
|
||||
for ntype in $(seq 65280 65289); do
|
||||
check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 60 || ret=1
|
||||
check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 120 || ret=1
|
||||
done
|
||||
# Wait at least one second
|
||||
sleep 1
|
||||
# Limited to 10 types - these should be cached
|
||||
for ntype in $(seq 65280 65289); do
|
||||
check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" "" 60 || ret=1
|
||||
check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" "" 120 || ret=1
|
||||
done
|
||||
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
@@ -356,11 +356,11 @@ echo_i "checking that priority NXDOMAIN names over the max-types-per-name limit
|
||||
|
||||
# Query for NXDOMAIN for items on our priority list - these should get cached
|
||||
for rrtype in AAAA MX NS; do
|
||||
check_manytypes 1 manytypes.big "${rrtype}" NOERROR big SOA 60 || ret=1
|
||||
check_manytypes 1 manytypes.big "${rrtype}" NOERROR big SOA 120 || ret=1
|
||||
done
|
||||
# Wait at least 1 second
|
||||
for rrtype in AAAA MX NS; do
|
||||
check_manytypes 2 manytypes.big "${rrtype}" NOERROR big SOA "" 60 || ret=1
|
||||
check_manytypes 2 manytypes.big "${rrtype}" NOERROR big SOA "" 120 || ret=1
|
||||
done
|
||||
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
@@ -372,11 +372,11 @@ ret=0
|
||||
echo_i "checking that priority name over the max-types-per-name get cached ($n)"
|
||||
|
||||
# Query for an item on our priority list - it should get cached
|
||||
check_manytypes 1 manytypes.big "A" NOERROR manytypes.big A 60 || ret=1
|
||||
check_manytypes 1 manytypes.big "A" NOERROR manytypes.big A 120 || ret=1
|
||||
# Wait at least 1 second
|
||||
sleep 1
|
||||
# Query the same name again - it should be in the cache
|
||||
check_manytypes 2 manytypes.big "A" NOERROR big manytypes.A "" 60 || ret=1
|
||||
check_manytypes 2 manytypes.big "A" NOERROR big manytypes.A "" 120 || ret=1
|
||||
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
@@ -389,7 +389,7 @@ ret=0
|
||||
echo_i "checking that priority name over the max-types-per-name don't get evicted ($n)"
|
||||
|
||||
# Query for an item on our priority list - it should get cached
|
||||
check_manytypes 1 manytypes.big "A" NOERROR manytypes.big A 60 || ret=1
|
||||
check_manytypes 1 manytypes.big "A" NOERROR manytypes.big A 120 || ret=1
|
||||
# Query for 10 more types - this should not evict A record
|
||||
for ntype in $(seq 65280 65289); do
|
||||
check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR manytypes.big || ret=1
|
||||
@@ -397,9 +397,9 @@ done
|
||||
# Wait at least 1 second
|
||||
sleep 1
|
||||
# Query the same name again - it should be in the cache
|
||||
check_manytypes 2 manytypes.big "A" NOERROR manytypes.big A "" 60 || ret=1
|
||||
check_manytypes 2 manytypes.big "A" NOERROR manytypes.big A "" 120 || ret=1
|
||||
# This one was first in the list and should have been evicted
|
||||
check_manytypes 2 manytypes.big "TYPE65280" NOERROR manytypes.big TYPE65280 60 || ret=1
|
||||
check_manytypes 2 manytypes.big "TYPE65280" NOERROR manytypes.big TYPE65280 120 || ret=1
|
||||
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
@@ -413,21 +413,21 @@ echo_i "checking that non-priority types cause eviction ($n)"
|
||||
|
||||
# Everything on top of that will cause the cache eviction
|
||||
for ntype in $(seq 65280 65299); do
|
||||
check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 60 || ret=1
|
||||
check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 120 || ret=1
|
||||
done
|
||||
# Wait at least one second
|
||||
sleep 1
|
||||
# These should have TTL != 60 now
|
||||
# These should have TTL != 120 now
|
||||
for ntype in $(seq 65290 65299); do
|
||||
check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" "" 60 || ret=1
|
||||
check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" "" 120 || ret=1
|
||||
done
|
||||
# These should have been evicted
|
||||
for ntype in $(seq 65280 65289); do
|
||||
check_manytypes 3 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 60 || ret=1
|
||||
check_manytypes 3 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 120 || ret=1
|
||||
done
|
||||
# These should have been evicted by the previous block
|
||||
for ntype in $(seq 65290 65299); do
|
||||
check_manytypes 4 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 60 || ret=1
|
||||
check_manytypes 4 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 120 || ret=1
|
||||
done
|
||||
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
@@ -442,25 +442,25 @@ echo_i "checking that signed names under the max-types-per-name limit get cached
|
||||
|
||||
# Go through the 10 items, this should result in 20 items (type + rrsig(type))
|
||||
for ntype in $(seq 65280 65289); do
|
||||
check_manytypes 1 manytypes.signed "TYPE${ntype}" NOERROR manytypes.signed "TYPE${ntype}" 60 || ret=1
|
||||
check_manytypes 1 manytypes.signed "TYPE${ntype}" NOERROR manytypes.signed "TYPE${ntype}" 120 || ret=1
|
||||
done
|
||||
|
||||
# Wait at least one second
|
||||
sleep 1
|
||||
|
||||
# These should have TTL != 60 now
|
||||
# These should have TTL != 120 now
|
||||
for ntype in $(seq 65285 65289); do
|
||||
check_manytypes 2 manytypes.signed "TYPE${ntype}" NOERROR manytypes.signed "TYPE${ntype}" "" 60 || ret=1
|
||||
check_manytypes 2 manytypes.signed "TYPE${ntype}" NOERROR manytypes.signed "TYPE${ntype}" "" 120 || ret=1
|
||||
done
|
||||
|
||||
# These should have been evicted
|
||||
for ntype in $(seq 65280 65284); do
|
||||
check_manytypes 3 manytypes.signed "TYPE${ntype}" NOERROR manytypes.signed "TYPE${ntype}" 60 || ret=1
|
||||
check_manytypes 3 manytypes.signed "TYPE${ntype}" NOERROR manytypes.signed "TYPE${ntype}" 120 || ret=1
|
||||
done
|
||||
|
||||
# These should have been evicted by the previous block
|
||||
for ntype in $(seq 65285 65289); do
|
||||
check_manytypes 4 manytypes.signed "TYPE${ntype}" NOERROR manytypes.signed "TYPE${ntype}" 60 || ret=1
|
||||
check_manytypes 4 manytypes.signed "TYPE${ntype}" NOERROR manytypes.signed "TYPE${ntype}" 120 || ret=1
|
||||
done
|
||||
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
@@ -475,12 +475,12 @@ echo_i "checking that lifting the limit will allow everything to get cached ($n)
|
||||
ns3_reset ns3/named6.conf.in
|
||||
|
||||
for ntype in $(seq 65280 65534); do
|
||||
check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 60 || ret=1
|
||||
check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" 120 || ret=1
|
||||
done
|
||||
# Wait at least one second
|
||||
sleep 1
|
||||
for ntype in $(seq 65280 65534); do
|
||||
check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" "" 60 || ret=1
|
||||
check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR manytypes.big "TYPE${ntype}" "" 120 || ret=1
|
||||
done
|
||||
|
||||
if [ $ret -ne 0 ]; then echo_i "failed"; fi
|
||||
|
||||
@@ -20,6 +20,8 @@ use IO::Socket;
|
||||
use Net::DNS;
|
||||
use Net::DNS::Packet;
|
||||
|
||||
print "Using Net::DNS $Net::DNS::VERSION\n";
|
||||
|
||||
my $localport = int($ENV{'PORT'});
|
||||
if (!$localport) { $localport = 5300; }
|
||||
|
||||
@@ -170,6 +172,15 @@ for (;;) {
|
||||
$packet->push("authority",
|
||||
new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0"));
|
||||
}
|
||||
} elsif ($qname eq "zoneversion") {
|
||||
$packet->push("authority", new Net::DNS::RR(". 300 SOA . . 0 0 0 0 0"));
|
||||
if ($Net::DNS::VERSION >= 1.49) {
|
||||
$packet->edns->option('ZONEVERSION' => [0, 1, '01022304'] )
|
||||
} elsif ($Net::DNS::VERSION >= 1.35) {
|
||||
$packet->edns->option('19' => {'BASE16' => '000101022304'} )
|
||||
} else {
|
||||
$packet->edns->option('19' => pack 'H*', '000101022304')
|
||||
}
|
||||
} else {
|
||||
# Data for the "bogus referrals" test
|
||||
$packet->push("authority", new Net::DNS::RR("below.www.example.com 300 NS ns.below.www.example.com"));
|
||||
|
||||
@@ -31,6 +31,7 @@ options {
|
||||
resolver-query-timeout 5000; # 5 seconds
|
||||
attach-cache "globalcache";
|
||||
max-recursion-queries 100;
|
||||
request-zoneversion yes;
|
||||
};
|
||||
|
||||
trust-anchors { };
|
||||
|
||||
@@ -4,26 +4,21 @@
|
||||
* 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
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/*! \file dns/zonekey.h */
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <dns/types.h>
|
||||
|
||||
bool
|
||||
dns_zonekey_iszonekey(dns_rdata_t *keyrdata);
|
||||
/*%<
|
||||
* Determines if the key record contained in the rdata is a zone key.
|
||||
*
|
||||
* Requires:
|
||||
* 'keyrdata' is not NULL.
|
||||
*/
|
||||
options {
|
||||
query-source address 10.53.0.11;
|
||||
notify-source 10.53.0.11;
|
||||
transfer-source 10.53.0.11;
|
||||
port @PORT@;
|
||||
pid-file "named.pid";
|
||||
listen-on { 10.53.0.11; };
|
||||
listen-on-v6 { none; };
|
||||
recursion no;
|
||||
dnssec-validation no;
|
||||
};
|
||||
@@ -26,6 +26,15 @@ options {
|
||||
querylog yes;
|
||||
prefetch 4 10;
|
||||
responselog yes;
|
||||
request-nsid yes;
|
||||
request-zoneversion yes;
|
||||
};
|
||||
|
||||
// Don't break tests which depend on ans10 by requesting
|
||||
// zoneversion or nsid
|
||||
server 10.53.0.10 {
|
||||
request-nsid no;
|
||||
request-zoneversion no;
|
||||
};
|
||||
|
||||
include "trusted.conf";
|
||||
|
||||
@@ -24,5 +24,6 @@ copy_setports ns5/named.conf.in ns5/named.conf
|
||||
copy_setports ns6/named.conf.in ns6/named.conf
|
||||
copy_setports ns7/named1.conf.in ns7/named.conf
|
||||
copy_setports ns9/named.conf.in ns9/named.conf
|
||||
copy_setports ns11/named.conf.in ns11/named.conf
|
||||
|
||||
(cd ns6 && $SHELL keygen.sh)
|
||||
|
||||
@@ -43,6 +43,12 @@ grep "status: NOERROR" dig.out.ns1.test${n} >/dev/null || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
rndccmd 10.53.0.1 stats || ret=1 # Get the responses, RTT and timeout statistics before the following timeout tests
|
||||
grep -F 'responses received' ns1/named.stats >ns1/named.stats.responses-before || true
|
||||
grep -F 'queries with RTT' ns1/named.stats >ns1/named.stats.rtt-before || true
|
||||
grep -F 'query timeouts' ns1/named.stats >ns1/named.stats.timeouts-before || true
|
||||
mv ns1/named.stats ns1/named.stats-before
|
||||
|
||||
# 'resolver-query-timeout' is set to 5 seconds in ns1, so dig with a lower
|
||||
# timeout value should give up earlier than that.
|
||||
n=$((n + 1))
|
||||
@@ -66,6 +72,20 @@ grep -F "EDE: 22 (No Reachable Authority)" dig.out.ns1.test${n} >/dev/null || re
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "checking that the timeout didn't skew the resolver responses counters and did update the timeout counter ($n)"
|
||||
ret=0
|
||||
rndccmd 10.53.0.1 stats || ret=1
|
||||
grep -F 'responses received' ns1/named.stats >ns1/named.stats.responses-after || true
|
||||
grep -F 'queries with RTT' ns1/named.stats >ns1/named.stats.rtt-after || true
|
||||
grep -F 'query timeouts' ns1/named.stats >ns1/named.stats.timeouts-after || true
|
||||
mv ns1/named.stats ns1/named.stats-after
|
||||
diff ns1/named.stats.responses-before ns1/named.stats.responses-after >/dev/null || ret=1
|
||||
diff ns1/named.stats.rtt-before ns1/named.stats.rtt-after >/dev/null || ret=1
|
||||
diff ns1/named.stats.timeouts-before ns1/named.stats.timeouts-after >/dev/null && ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
# 'resolver-query-timeout' is set to 5 seconds in ns1, so named should
|
||||
# interrupt the non-responsive query and send a SERVFAIL answer before dig's
|
||||
# own timeout fires, which is set to 7 seconds. This time, exampleudp.net is
|
||||
@@ -729,10 +749,10 @@ if ${FEATURETEST} --enable-querytrace; then
|
||||
grep "status: SERVFAIL" dig.ns5.out.${n} >/dev/null || ret=1
|
||||
check_namedrun() {
|
||||
nextpartpeek ns5/named.run >nextpart.out.${n}
|
||||
grep 'resolving tcpalso.no-questions/A for [^:]*: empty question section, accepting it anyway as TC=1' nextpart.out.${n} >/dev/null || return 1
|
||||
grep '(tcpalso.no-questions/A): connecting via TCP' nextpart.out.${n} >/dev/null || return 1
|
||||
grep 'resolving tcpalso.no-questions/A for [^:]*: empty question section$' nextpart.out.${n} >/dev/null || return 1
|
||||
grep '(tcpalso.no-questions/A): nextitem' nextpart.out.${n} >/dev/null || return 1
|
||||
grep 'resolving tcpalso.no-questions/NS for [^:]*: empty question section, accepting it anyway as TC=1' nextpart.out.${n} >/dev/null || return 1
|
||||
grep '(tcpalso.no-questions/NS): connecting via TCP' nextpart.out.${n} >/dev/null || return 1
|
||||
grep 'resolving tcpalso.no-questions/NS for [^:]*: empty question section$' nextpart.out.${n} >/dev/null || return 1
|
||||
grep '(tcpalso.no-questions/NS): nextitem' nextpart.out.${n} >/dev/null || return 1
|
||||
return 0
|
||||
}
|
||||
retry_quiet 12 check_namedrun || ret=1
|
||||
@@ -881,6 +901,23 @@ test ${lines:-1} -ne 0 && ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "check that received ZONEVERSION is logged ($n)"
|
||||
ret=0
|
||||
pat="received ZONEVERSION serial 2010 from 10.53.0.4#[0-9]* for mixedttl.tld/TXT zone tld"
|
||||
grep "$pat" ns5/named.run >/dev/null || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "check that received ZONEVERSION is logged non serial ($n)"
|
||||
ret=0
|
||||
dig_with_opts +tcp @10.53.0.1 zoneversion >dig.out.${n} || ret=1
|
||||
pat='received ZONEVERSION type 1 value 01022304 (\.\.#\.) from 10.53.0.2#[0-9]* for zoneversion/A zone \.'
|
||||
grep "$pat" ns1/named.run >/dev/null || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "check resolver behavior when FORMERR for EDNS options happens (${n})"
|
||||
ret=0
|
||||
@@ -1015,5 +1052,14 @@ ttl=$(awk '{print $2}' dig.ns1.out.${n})
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "client requests recursion but it is disabled - expect EDE 20 code with REFUSED($n)"
|
||||
ret=0
|
||||
dig_with_opts +recurse www.isc.org @10.53.0.11 a >dig.out.ns11.test${n} || ret=1
|
||||
grep "status: REFUSED" dig.out.ns11.test${n} >/dev/null || ret=1
|
||||
grep -F "EDE: 20 (Not Authoritative)" dig.out.ns11.test${n} >/dev/null || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
echo_i "exit status: $status"
|
||||
[ $status -eq 0 ] || exit 1
|
||||
|
||||
@@ -21,6 +21,7 @@ pytestmark = pytest.mark.extra_artifacts(
|
||||
"nextpart.out.*",
|
||||
"ans*/ans.run",
|
||||
"ans*/query.log",
|
||||
"ns1/named.stats*",
|
||||
"ns4/tld.db",
|
||||
"ns5/trusted.conf",
|
||||
"ns6/K*",
|
||||
|
||||
@@ -111,8 +111,8 @@ def test_rpz_passthru_logging():
|
||||
expected_rcode=dns.rcode.NOERROR,
|
||||
)
|
||||
assert res_allowed_any.answer == [
|
||||
dns.rrset.from_text("allowed.", 300, "IN", "NS", "ns1.allowed."),
|
||||
dns.rrset.from_text("allowed.", 300, "IN", "A", "10.53.0.2"),
|
||||
dns.rrset.from_text("allowed.", 300, "IN", "NS", "ns1.allowed."),
|
||||
]
|
||||
# The comparison above doesn't compare the TTL values, and we want to
|
||||
# make sure that the "passthru" rpz doesn't cap the TTL with max-policy-ttl.
|
||||
|
||||
@@ -115,10 +115,12 @@ sleep 2
|
||||
# stale for somewhere between 3500-3599 seconds.
|
||||
echo_i "check rndc dump stale data.example ($n)"
|
||||
rndc_dumpdb ns1 || ret=1
|
||||
awk '/; stale since [0-9]*/ { x=$0; getline; print x, $0}' ns1/named_dump.db.test$n \
|
||||
# add in inherited owner names
|
||||
awk '$1 ~ /^[0-9][0-9]*$/ { $0 = last " " $0 } $1 != ";" { last = $1 } { print }' ns1/named_dump.db.test$n >named_dump.db.test$n
|
||||
awk '/; stale since [0-9]*/ { x=$0; getline; print x, $0}' named_dump.db.test$n \
|
||||
| grep "; stale since [0-9]* data\.example.*3[56]...*TXT.*A text record with a 2 second ttl" >/dev/null 2>&1 || ret=1
|
||||
# Also make sure the not expired data does not have a stale comment.
|
||||
awk '/; authanswer/ { x=$0; getline; print x, $0}' ns1/named_dump.db.test$n \
|
||||
awk '/; authanswer/ { x=$0; getline; print x, $0}' named_dump.db.test$n \
|
||||
| grep "; authanswer longttl\.example.*[56]...*TXT.*A text record with a 600 second ttl" >/dev/null 2>&1 || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
@@ -1664,16 +1666,15 @@ status=$((status + ret))
|
||||
# Check that expired records are dumped.
|
||||
echo_i "check rndc dump expired data.example ($n)"
|
||||
ret=0
|
||||
awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
|
||||
| grep "; expired (awaiting cleanup) data\.example\..*A text record with a 2 second ttl" >/dev/null 2>&1 || ret=1
|
||||
awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
|
||||
| grep "; expired (awaiting cleanup) nodata\.example\." >/dev/null 2>&1 || ret=1
|
||||
awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
|
||||
| grep "; expired (awaiting cleanup) nxdomain\.example\." >/dev/null 2>&1 || ret=1
|
||||
awk '/; expired/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
|
||||
| grep "; expired (awaiting cleanup) othertype\.example\." >/dev/null 2>&1 || ret=1
|
||||
# add in inherited owner names
|
||||
awk '$1 ~ /^[0-9][0-9]*$/ { $0 = last " " $0 } $1 != ";" { last = $1 } { print }' ns5/named_dump.db.test$n >named_dump.db.test$n
|
||||
# extract expired records
|
||||
awk '/; expired/ { x=$0; getline; print x, $0}' named_dump.db.test$n >expired.test$n
|
||||
grep "; expired (awaiting cleanup) data\.example\..*A text record with a 2 second ttl" expired.test$n >/dev/null 2>&1 || ret=1
|
||||
grep "; expired (awaiting cleanup) nodata\.example\." expired.test$n >/dev/null 2>&1 || ret=1
|
||||
grep "; expired (awaiting cleanup) nxdomain\.example\." expired.test$n >/dev/null 2>&1 || ret=1
|
||||
# Also make sure the not expired data does not have an expired comment.
|
||||
awk '/; authanswer/ { x=$0; getline; print x, $0}' ns5/named_dump.db.test$n \
|
||||
awk '/; authanswer/ { x=$0; getline; print x, $0}' named_dump.db.test$n \
|
||||
| grep "; authanswer longttl\.example.*A text record with a 600 second ttl" >/dev/null 2>&1 || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
@@ -14,6 +14,8 @@ import pytest
|
||||
pytestmark = pytest.mark.extra_artifacts(
|
||||
[
|
||||
"dig.out.*",
|
||||
"expired.test*",
|
||||
"named_dump.db.test*",
|
||||
"rndc.out.*",
|
||||
"ans*/ans.run",
|
||||
"ns*/named.stats*",
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -414,10 +414,10 @@ for ns in 2 4 5 6; do
|
||||
check_status NOERROR dig.out.ns${ns}.test$n || ret=1
|
||||
if [ ${synth} = yes ]; then
|
||||
check_synth_cname b.wild-cname.example. dig.out.ns${ns}.test$n || ret=1
|
||||
nextpart ns1/named.run | grep b.wild-cname.example/A >/dev/null && ret=1
|
||||
nextpart ns1/named.run | grep b.wild-cname.example/NS >/dev/null && ret=1
|
||||
else
|
||||
check_nosynth_cname b.wild-cname.example. dig.out.ns${ns}.test$n || ret=1
|
||||
nextpart ns1/named.run | grep b.wild-cname.example/A >/dev/null || ret=1
|
||||
nextpart ns1/named.run | grep b.wild-cname.example/NS >/dev/null || ret=1
|
||||
fi
|
||||
grep "ns1.example.*.IN.A" dig.out.ns${ns}.test$n >/dev/null || ret=1
|
||||
digcomp wildcname.out dig.out.ns${ns}.test$n || ret=1
|
||||
@@ -470,6 +470,7 @@ for ns in 2 4 5 6; do
|
||||
check_nosynth_aaaa b.wild-2-nsec-afterdata.example. dig.out.a.ns${ns}.test$n || ret=1
|
||||
#
|
||||
nextpart ns1/named.run >/dev/null
|
||||
sleep 1
|
||||
dig_with_opts b.wild-2-nsec-afterdata.example. @10.53.0.${ns} TLSA >dig.out.ns${ns}.test$n || ret=1
|
||||
check_ad_flag $ad dig.out.ns${ns}.test$n || ret=1
|
||||
check_status NOERROR dig.out.ns${ns}.test$n || ret=1
|
||||
@@ -531,7 +532,7 @@ for ns in 2 4 5 6; do
|
||||
check_ad_flag no dig.out.ns${ns}.test$n || ret=1
|
||||
check_status NOERROR dig.out.ns${ns}.test$n || ret=1
|
||||
check_nosynth_cname b.wild-cname.insecure.example dig.out.ns${ns}.test$n || ret=1
|
||||
nextpart ns1/named.run | grep b.wild-cname.insecure.example/A >/dev/null || ret=1
|
||||
nextpart ns1/named.run | grep b.wild-cname.insecure.example/NS >/dev/null || ret=1
|
||||
grep "ns1.insecure.example.*.IN.A" dig.out.ns${ns}.test$n >/dev/null || ret=1
|
||||
digcomp insecure.wildcname.out dig.out.ns${ns}.test$n || ret=1
|
||||
n=$((n + 1))
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
#
|
||||
m4_define([bind_VERSION_MAJOR], 9)dnl
|
||||
m4_define([bind_VERSION_MINOR], 21)dnl
|
||||
m4_define([bind_VERSION_PATCH], 6)dnl
|
||||
m4_define([bind_VERSION_PATCH], 7)dnl
|
||||
m4_define([bind_VERSION_EXTRA], -dev)dnl
|
||||
m4_define([bind_DESCRIPTION], [(Development Release)])dnl
|
||||
m4_define([bind_SRCID], [m4_esyscmd_s([git rev-parse --short HEAD | cut -b1-7])])dnl
|
||||
|
||||
@@ -18,6 +18,7 @@ Changelog
|
||||
development. Regular users should refer to :ref:`Release Notes <relnotes>`
|
||||
for changes relevant to them.
|
||||
|
||||
.. include:: ../changelog/changelog-9.21.6.rst
|
||||
.. include:: ../changelog/changelog-9.21.5.rst
|
||||
.. include:: ../changelog/changelog-9.21.4.rst
|
||||
.. include:: ../changelog/changelog-9.21.3.rst
|
||||
|
||||
@@ -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/",
|
||||
|
||||
@@ -127,3 +127,7 @@
|
||||
|
||||
``zoneload``
|
||||
Loading of zones and creation of automatic empty zones.
|
||||
|
||||
``zoneversion``
|
||||
ZONEVERSION options received from upstream servers.
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ The list of known issues affecting the latest version in the 9.21 branch can be
|
||||
found at
|
||||
https://gitlab.isc.org/isc-projects/bind9/-/wikis/Known-Issues-in-BIND-9.21
|
||||
|
||||
.. include:: ../notes/notes-9.21.6.rst
|
||||
.. include:: ../notes/notes-9.21.5.rst
|
||||
.. include:: ../notes/notes-9.21.4.rst
|
||||
.. include:: ../notes/notes-9.21.3.rst
|
||||
|
||||
+29
-3
@@ -2165,6 +2165,14 @@ Boolean Options
|
||||
ultimate primary should be set to still send NOTIFY messages to all the name servers
|
||||
listed in the NS RRset.
|
||||
|
||||
.. namedconf:statement:: provide-zoneversion
|
||||
:tags: transfer
|
||||
:short: Controls the return EDNS ZONEVERSION answers.
|
||||
|
||||
If ``yes`` EDNS ZONEVERSION answers will be returned otherwise
|
||||
not for primary, secondary and mirror zones. The default is
|
||||
``yes``.
|
||||
|
||||
.. namedconf:statement:: recursion
|
||||
:tags: query
|
||||
:short: Defines whether recursion and caching are allowed.
|
||||
@@ -2188,6 +2196,18 @@ Boolean Options
|
||||
option in its response, then its contents are logged in the ``nsid``
|
||||
category at level ``info``. The default is ``no``.
|
||||
|
||||
.. namedconf:statement:: request-zoneversion
|
||||
:tags: query
|
||||
:short: Controls whether an empty EDNS(0) ZONEVERSION option is sent with all queries to authoritative name servers during iterative resolution.
|
||||
|
||||
If ``yes``, then an empty EDNS(0) ZONEVERSION option is sent
|
||||
with all queries to authoritative name servers during iterative
|
||||
resolution. If the authoritative server returns an ZONEVERSION
|
||||
option in its response, then its contents are logged in the
|
||||
``zoneversion`` category at level ``info``. If the NSID has
|
||||
also been requested and it is returned then that is appended to
|
||||
the log message. The default is ``no``.
|
||||
|
||||
.. namedconf:statement:: require-cookie
|
||||
:tags: query
|
||||
:short: Controls whether responses without a server cookie are accepted.
|
||||
@@ -3660,9 +3680,13 @@ system.
|
||||
after 20 minutes if it has remained unchanged.
|
||||
|
||||
If :any:`max-clients-per-query` is set to zero, there is no upper bound, other
|
||||
than that imposed by :any:`recursive-clients`. If :any:`clients-per-query` is
|
||||
set to zero, :any:`max-clients-per-query` no longer applies and there is no
|
||||
upper bound, other than that imposed by :any:`recursive-clients`.
|
||||
than that imposed by :any:`recursive-clients`. If the option is set to a
|
||||
lower value than :any:`clients-per-query`, the value is adjusted to
|
||||
:any:`clients-per-query`.
|
||||
|
||||
If :any:`clients-per-query` is set to zero, :any:`max-clients-per-query` no
|
||||
longer applies and there is no upper bound, other than that imposed by
|
||||
:any:`recursive-clients`.
|
||||
|
||||
.. namedconf:statement:: max-validations-per-fetch
|
||||
:tags: server
|
||||
@@ -5597,11 +5621,13 @@ and :namedconf:ref:`options` blocks:
|
||||
- :namedconf:ref:`notify-source-v6`
|
||||
- :namedconf:ref:`notify-source`
|
||||
- :namedconf:ref:`provide-ixfr`
|
||||
- :namedconf:ref:`provide-zoneversion`
|
||||
- :namedconf:ref:`query-source-v6`
|
||||
- :namedconf:ref:`query-source`
|
||||
- :namedconf:ref:`request-expire`
|
||||
- :namedconf:ref:`request-ixfr`
|
||||
- :namedconf:ref:`request-nsid`
|
||||
- :namedconf:ref:`request-zoneversion`
|
||||
- :namedconf:ref:`require-cookie`
|
||||
- :namedconf:ref:`send-cookie`
|
||||
- :namedconf:ref:`transfer-format`
|
||||
|
||||
@@ -0,0 +1,478 @@
|
||||
.. 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.
|
||||
|
||||
BIND 9.21.6
|
||||
-----------
|
||||
|
||||
New Features
|
||||
~~~~~~~~~~~~
|
||||
|
||||
- Implement the min-transfer-rate-in configuration option.
|
||||
``a282f1ba3f``
|
||||
|
||||
A new option 'min-transfer-rate-in <bytes> <minutes>' has been added
|
||||
to the view and zone configurations. It can abort incoming zone
|
||||
transfers which run very slowly due to network related issues, for
|
||||
example. The default value is set to 10240 bytes in 5 minutes.
|
||||
:gl:`#3914` :gl:`!9098`
|
||||
|
||||
- Add digest methods for SIG and RRSIG. ``fd48df20f3``
|
||||
|
||||
ZONEMD digests RRSIG records and potentially digests SIG record. Add
|
||||
digests methods for both record types. :gl:`#5219` :gl:`!10217`
|
||||
|
||||
- Add HTTPS record query to host command line tool. ``d34414c47b``
|
||||
|
||||
The host command was extended to also query for the HTTPS RR type by
|
||||
default. :gl:`!8642`
|
||||
|
||||
Removed Features
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
- Clean up unnecessary code in qpcache. ``74c9ff384e``
|
||||
|
||||
Removed some code from the cache database implementation that was left
|
||||
over from before it and the zone database implementation were
|
||||
separated. :gl:`!9991`
|
||||
|
||||
- Cleanup isc/util.h header and friends. ``239712df16``
|
||||
|
||||
Cleanup short list macros from <isc/util.h>, remove two unused
|
||||
headers, move locking macros to respective headers and use only the
|
||||
C11 static assertion. :gl:`!10196`
|
||||
|
||||
- Remove check for the mandatory IPv6 support. ``daa9c17905``
|
||||
|
||||
IPv6 Advanced Socket API (:rfc:`3542`) is a hard requirement, remove
|
||||
the autoconf check to speed up the ./configure run a little bit.
|
||||
:gl:`!10201`
|
||||
|
||||
- Remove log initialization checks from named. ``1b3e7f52ec``
|
||||
|
||||
Logging initialization check is now redundant as there is a default
|
||||
global log context created during libisc's constructor.
|
||||
|
||||
`isc_log` calls can safely be made at any time outside libisc's
|
||||
constructor. :gl:`!10186`
|
||||
|
||||
Feature Changes
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
- Refactor and simplify isc_symtab. ``5559539eb0``
|
||||
|
||||
This commit does several changes to isc_symtab:
|
||||
|
||||
1. Rewrite the isc_symtab to internally use isc_hashmap instead of
|
||||
hand-stiched hashtable.
|
||||
|
||||
2. Create a new isc_symtab_define_and_return() api, which returns
|
||||
the already defined symvalue on ISC_R_EXISTS; this allows users of
|
||||
the API to skip the isc_symtab_lookup()+isc_symtab_define() calls
|
||||
and directly call isc_symtab_define_and_return().
|
||||
|
||||
3. Merge isccc_symtab into isc_symtab - the only missing function
|
||||
was isccc_symtab_foreach() that was merged into isc_symtab API.
|
||||
|
||||
4. Add full set of unit tests for the isc_symtab API. :gl:`#5103`
|
||||
:gl:`!9921`
|
||||
|
||||
- Drop malformed notify messages early instead of decompressing them.
|
||||
``7fce7707db``
|
||||
|
||||
The DNS header shows if a message has multiple questions or invalid
|
||||
NOTIFY sections. We can drop these messages early, right after parsing
|
||||
the question. This matches RFC 9619 for multi-question messages and
|
||||
Unbound's handling of NOTIFY. We still parse the question to include
|
||||
it in our FORMERR response.
|
||||
|
||||
Add drop_msg_early() function to check for these conditions: -
|
||||
Messages with more than one question, as required by RFC 9619 - NOTIFY
|
||||
query messages containing answer sections (like Unbound) - NOTIFY
|
||||
messages containing authority sections (like Unbound) :gl:`#5158`,
|
||||
#3656 :gl:`!10056`
|
||||
|
||||
- Cleanup parts of the isc_mem API. ``4ba1ccfa2e``
|
||||
|
||||
This MR changes custom attach/detach implementation with refcount
|
||||
macros, replaces isc_mem_destroy() with isc_mem_detach(), and does
|
||||
various small cleanups. :gl:`!9456`
|
||||
|
||||
- Move the library initialization and shutdown to executables.
|
||||
``6e0c1f151c``
|
||||
|
||||
Instead of relying on unreliable order of execution of the library
|
||||
constructors and destructors, move them to individual binaries. The
|
||||
advantage is that the execution time and order will remain constant
|
||||
and will not depend on the dynamic load dependency solver.
|
||||
:gl:`!10069`
|
||||
|
||||
- Reduce memory used to store DNS names. ``24db1b1a8a``
|
||||
|
||||
The memory used to internally store the DNS names has been reduced.
|
||||
:gl:`!10140`
|
||||
|
||||
- Unify fips handling to isc_crypto and make the toggle one way.
|
||||
``3de629d6b7``
|
||||
|
||||
Since algorithm fetching is handled purely in libisc, FIPS mode
|
||||
toggling can be purely done in within the library instead of provider
|
||||
fetching in the binary for OpenSSL >=3.0.
|
||||
|
||||
Disabling FIPS mode isn't a realistic requirement and isn't done
|
||||
anywhere in the codebase. Make the FIPS mode toggle enable-only to
|
||||
reflect the situation. :gl:`!9920`
|
||||
|
||||
Bug Fixes
|
||||
~~~~~~~~~
|
||||
|
||||
- Prevent a reference leak when using plugins. ``5604d3a44e``
|
||||
|
||||
The `NS_QUERY_DONE_BEGIN` and `NS_QUERY_DONE_SEND` plugin hooks could
|
||||
cause a reference leak if they returned `NS_HOOK_RETURN` without
|
||||
cleaning up the query context properly. :gl:`#2094` :gl:`!9971`
|
||||
|
||||
- Fix isc_quota bug. ``742d379d88``
|
||||
|
||||
Running jobs which were entered into the isc_quota queue is the
|
||||
responsibility of the isc_quota_release() function, which, when
|
||||
releasing a previously acquired quota, checks whether the queue is
|
||||
empty, and if it's not, it runs a job from the queue without touching
|
||||
the 'quota->used' counter. This mechanism is susceptible to a possible
|
||||
hangup of a newly queued job in case when between the time a decision
|
||||
has been made to queue it (because used >= max) and the time it was
|
||||
actually queued, the last quota was released. Since there is no more
|
||||
quotas to be released (unless arriving in the future), the newly
|
||||
entered job will be stuck in the queue.
|
||||
|
||||
Fix the issue by adding checks in both isc_quota_release() and
|
||||
isc_quota_acquire_cb() to make sure that the described hangup does not
|
||||
happen. Also see code comments. :gl:`#4965` :gl:`!10082`
|
||||
|
||||
- Fix dual-stack-servers configuration option. ``6af708f3b0``
|
||||
|
||||
The dual-stack-servers configuration option was not working as
|
||||
expected; the specified servers were not being used when they should
|
||||
have been, leading to resolution failures. This has been fixed.
|
||||
:gl:`#5019` :gl:`!9708`
|
||||
|
||||
- Implement sig0key-checks-limit and sig0message-checks-limit.
|
||||
``d78ebff861``
|
||||
|
||||
Previously a hard-coded limitation of maximum two key or message
|
||||
verification checks were introduced when checking the message's SIG(0)
|
||||
signature. It was done in order to protect against possible DoS
|
||||
attacks. The logic behind choosing the number 2 was that more than a
|
||||
single key should only be required during key rotations, and in that
|
||||
case two keys are enough. But later it became apparent that there are
|
||||
other use cases too where even more keys are required, see issue
|
||||
number #5050 in GitLab.
|
||||
|
||||
This change introduces two new configuration options for the views,
|
||||
`sig0key-checks-limit` and `sig0message-checks-limit`, which define
|
||||
how many keys are allowed to be checked to find a matching key, and
|
||||
how many message verifications are allowed to take place once a
|
||||
matching key has been found. The latter protects against expensive
|
||||
cryptographic operations when there are keys with colliding tags and
|
||||
algorithm numbers, with default being 2, and the former protects
|
||||
against a bit less expensive key parsing operations and defaults to
|
||||
16. :gl:`#5050` :gl:`!9967`
|
||||
|
||||
- Fix the data race causing a permanent active client increase.
|
||||
``479c366c2b``
|
||||
|
||||
Previously, a data race could cause a newly created fetch context for
|
||||
a new client to be used before it had been fully initialized, which
|
||||
would cause the query to become stuck; queries for the same data would
|
||||
be either paused indefinitely or dropped because of the
|
||||
`clients-per-query` limit. This has been fixed. :gl:`#5053`
|
||||
:gl:`!10146`
|
||||
|
||||
- Fix deferred validation of unsigned DS and DNSKEY records.
|
||||
``ebf1606f38``
|
||||
|
||||
When processing a query with the "checking disabled" bit set (CD=1),
|
||||
`named` stores the unvalidated result in the cache, marked "pending".
|
||||
When the same query is sent with CD=0, the cached data is validated,
|
||||
and either accepted as an answer, or ejected from the cache as
|
||||
invalid. This deferred validation was not attempted for DS and DNSKEY
|
||||
records if they had no cached signatures, causing spurious validation
|
||||
failures. We now complete the deferred validation in this scenario.
|
||||
|
||||
Also, if deferred validation fails, we now re-query the data to find
|
||||
out whether the zone has been corrected since the invalid data was
|
||||
cached. :gl:`#5066` :gl:`!10104`
|
||||
|
||||
- When recording an rr trace, use libtool. ``6320586df0``
|
||||
|
||||
When a system test is run with the `USE_RR` environment variable set
|
||||
to 1, an `rr` trace is now correctly generated for each instance of
|
||||
`named`. :gl:`#5079` :gl:`!10197`
|
||||
|
||||
- Do not cache signatures for rejected data. ``fc3a4d6f89``
|
||||
|
||||
The cache has been updated so that if new data is rejected - for
|
||||
example, because there was already existing data at a higher trust
|
||||
level - then its covering RRSIG will also be rejected. :gl:`#5132`
|
||||
:gl:`!9999`
|
||||
|
||||
- Fix wrong logging severity in do_nsfetch() ``1f6a16e6d0``
|
||||
|
||||
ISC_LOG_WARNING was used while ISC_LOG_DEBUG(3) was implied.
|
||||
:gl:`#5145` :gl:`!10017`
|
||||
|
||||
- Fix RPZ race condition during a reconfiguration. ``5ba811bea2``
|
||||
|
||||
With RPZ in use, `named` could terminate unexpectedly because of a
|
||||
race condition when a reconfiguration command was received using
|
||||
`rndc`. This has been fixed. :gl:`#5146` :gl:`!10079`
|
||||
|
||||
- "CNAME and other data check" not applied to all types. ``b694acbe45``
|
||||
|
||||
An incorrect optimization caused "CNAME and other data" errors not to
|
||||
be detected if certain types were at the same node as a CNAME. This
|
||||
has been fixed. :gl:`#5150` :gl:`!10033`
|
||||
|
||||
- Use named Service Parameter Keys (SvcParamKeys) by default.
|
||||
``3f61a87be3``
|
||||
|
||||
When converting SVCB records to text representation `named` now uses
|
||||
named `SvcParamKeys` values unless backward-compatible mode is
|
||||
activated, in which case the values which were not defined initially
|
||||
in RFC9460 and were added later (see [1]) are converted to opaque
|
||||
"keyNNNN" syntax, like, for example, "key7" instead of "dohpath".
|
||||
|
||||
Also a new `+[no]svcparamkeycompat` option is implemented for `dig`,
|
||||
which enables the backward-compatible mode and uses the opaque syntax,
|
||||
if required for interoperability with other software or scripts. By
|
||||
default, the compatibility mode is disabled.
|
||||
|
||||
[1] https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml
|
||||
:gl:`#5156` :gl:`!10085`
|
||||
|
||||
- Relax private DNSKEY and RRSIG constraints. ``1bc7016d7a``
|
||||
|
||||
DNSKEY, KEY, RRSIG and SIG constraints have been relaxed to allow
|
||||
empty key and signature material after the algorithm identifier for
|
||||
PRIVATEOID and PRIVATEDNS. It is arguable whether this falls within
|
||||
the expected use of these types as no key material is shared and the
|
||||
signatures are ineffective but these are private algorithms and they
|
||||
can be totally insecure. :gl:`#5167` :gl:`!10083`
|
||||
|
||||
- Delete dead nodes when committing a new version. ``67255da4b3``
|
||||
|
||||
In the qpzone implementation of `dns_db_closeversion()`, if there are
|
||||
changed nodes that have no remaining data, delete them. :gl:`#5169`
|
||||
:gl:`!10089`
|
||||
|
||||
- Revert "Delete dead nodes when committing a new version"
|
||||
``b652d5327c``
|
||||
|
||||
This reverts commit 67255da4b376f65138b299dcd5eb6a3b7f9735a9,
|
||||
reversing changes made to 74c9ff384e695d1b27fa365d1fee84576f869d4c.
|
||||
:gl:`#5169` :gl:`!10224`
|
||||
|
||||
- Fix dns_qp_insert() checks in qpzone. ``d6b63210a8``
|
||||
|
||||
Remove code in the QP zone database to handle failures of
|
||||
`dns_qp_insert()` which can't actually happen. :gl:`#5171`
|
||||
:gl:`!10088`
|
||||
|
||||
- Remove NSEC/DS/NSEC3 RRSIG check from dns_message_parse.
|
||||
``f0785fedf1``
|
||||
|
||||
Previously, when parsing responses, named incorrectly rejected
|
||||
responses without matching RRSIG records for NSEC/DS/NSEC3 records in
|
||||
the authority section. This rejection, if appropriate, should have
|
||||
been left for the validator to determine and has been fixed.
|
||||
:gl:`#5185` :gl:`!10125`
|
||||
|
||||
- Fix TTL issue with ANY queries processed through RPZ "passthru"
|
||||
``23c1fbc609``
|
||||
|
||||
Answers to an "ANY" query which were processed by the RPZ "passthru"
|
||||
policy had the response-policy's `max-policy-ttl` value unexpectedly
|
||||
applied. This has been fixed. :gl:`#5187` :gl:`!10176`
|
||||
|
||||
- Save time when creating a slab from another slab. ``cf981ab13b``
|
||||
|
||||
The `dns_rdataslab_fromrdataset()` function creates a slab from an
|
||||
rdataset. If the source rdataset already uses a slab, then no
|
||||
processing is necessary; we can just copy the existing slab to a new
|
||||
location. :gl:`#5188` :gl:`!10162`
|
||||
|
||||
- Dnssec-signzone needs to check for a NULL key when setting offline.
|
||||
``26f8ee7229``
|
||||
|
||||
dnssec-signzone could dereference a NULL key pointer when resigning a
|
||||
zone. This has been fixed. :gl:`#5192` :gl:`!10161`
|
||||
|
||||
- Acquire the database reference before possibly last node release.
|
||||
``c4868b5bd9``
|
||||
|
||||
Acquire the database reference in the detachnode() to prevent the last
|
||||
reference to be release while the NODE_LOCK being locked. The
|
||||
NODE_LOCK is locked/unlocked inside the RCU critical section, thus it
|
||||
is most probably this should not pose a problem as the database uses
|
||||
call_rcu memory reclamation, but this it is still safer to acquire the
|
||||
reference before releasing the node. :gl:`#5194` :gl:`!10155`
|
||||
|
||||
- Fix a logic error in cache_name() ``02ef8ff01c``
|
||||
|
||||
A change in 6aba56ae8 (checking whether a rejected RRset was identical
|
||||
to the data it would have replaced, so that we could still cache a
|
||||
signature) inadvertently introduced cases where processing of a
|
||||
response would continue when previously it would have been skipped.
|
||||
:gl:`#5197` :gl:`!10157`
|
||||
|
||||
- Fix a bug in the statistics channel when querying zone transfers
|
||||
information. ``e02d73e7e3``
|
||||
|
||||
When querying zone transfers information from the statistics channel
|
||||
there was a rare possibility that `named` could terminate unexpectedly
|
||||
if a zone transfer was in a state when transferring from all the
|
||||
available primary servers had failed earlier. This has been fixed.
|
||||
:gl:`#5198` :gl:`!10182`
|
||||
|
||||
- Fix assertion failure when dumping recursing clients. ``796b662b92``
|
||||
|
||||
Previously, if a new counter was added to the hashtable while dumping
|
||||
recursing clients via the `rndc recursing` command, and
|
||||
`fetches-per-zone` was enabled, an assertion failure could occur. This
|
||||
has been fixed. :gl:`#5200` :gl:`!10164`
|
||||
|
||||
- Validating ADB fetches could cause a crash in import_rdataset()
|
||||
``49ccbe857a``
|
||||
|
||||
Previously, in some cases, the resolver could return rdatasets of type
|
||||
CNAME or DNAME without the result code being set to `DNS_R_CNAME` or
|
||||
`DNS_R_DNAME`. This could trigger an assertion failure in the ADB. The
|
||||
resolver error has been fixed. :gl:`#5201` :gl:`!10172`
|
||||
|
||||
- Call isc__iterated_hash_initialize in isc__work_cb. ``f3458fdf43``
|
||||
|
||||
isc_iterated_hash didn't work in offloaded threads as the per thread
|
||||
initialisation has not been done. This has been fixed. :gl:`#5214`
|
||||
:gl:`!10206`
|
||||
|
||||
- Fix a bug in get_request_transport_type() ``db5166ab99``
|
||||
|
||||
When `dns_remote_done()` is true, calling `dns_remote_curraddr()`
|
||||
asserts. Add a `dns_remote_curraddr()` check before calling
|
||||
`dns_remote_curraddr()`. :gl:`#5215` :gl:`!10222`
|
||||
|
||||
- Clean up dns_rdataslab module. ``948f8d7a98``
|
||||
|
||||
Rdata slabs used in the QP databases are usually prepended with a slab
|
||||
header, but are sometimes "raw", containing only the rdata and no
|
||||
header. Previously, to allow for them to be used both ways, functions
|
||||
that operated on them took a `reservelen` argument, which would be set
|
||||
to either the header length or to zero, and skipped over that many
|
||||
bytes at the beginning of the buffer. Most such functions were never
|
||||
used on the raw form. To make the code clearer, each of these
|
||||
functions now operates on full slabs with headers, and an alternate
|
||||
"raw" version of the function has been added in cases where that was
|
||||
needed.
|
||||
|
||||
In addition, the `dns_rdataslab_merge()` and `_subtract()` functions
|
||||
have been rewritten for clarity and efficiency, and a minor bug has
|
||||
been fixed in `dns_rdataslab_equal()` and `_equalx()`, which could
|
||||
cause an incorrect result if both slabs being compared had zero
|
||||
length. :gl:`!10084`
|
||||
|
||||
- Dump the active resolver fetches from dns_resolver_dumpfetches()
|
||||
``5d0c347e75``
|
||||
|
||||
Previously, active resolver fetches were only dumped when the
|
||||
`fetches-per-zone` configuration option was enabled. Now, active
|
||||
resolver fetches are dumped along with the number of
|
||||
`clients-per-server` counters per resolver fetch. :gl:`!10107`
|
||||
|
||||
- Fix the foundname vs dcname madness in qpcache_findzonecut()
|
||||
``4e68dbf194``
|
||||
|
||||
The qpcache_findzonecut() accepts two "foundnames": 'foundname' and
|
||||
'dcname' could be NULL. Originally, when 'dcname' would be NULL, the
|
||||
'dcname' would be set to 'foundname' which basically means that we
|
||||
were copying the .ndata over itself for no apparent reason.
|
||||
:gl:`!10049`
|
||||
|
||||
- Post [CVE-2024-12705] Performance Drop Fixes, Part 2. ``c8104daf8d``
|
||||
|
||||
This merge request addresses several key performance bottlenecks in
|
||||
the DoH (DNS over HTTPS) implementation by introducing significant
|
||||
optimizations and improvements.
|
||||
|
||||
### Key Improvements
|
||||
|
||||
1. **Simplification and Optimisation of `http_do_bio()` Function**:
|
||||
- The code flow in the `http_do_bio()` function has been significantly
|
||||
simplified. 2. **Flushing HTTP Write Buffer on Outgoing DNS
|
||||
Messages**: - The buffer is flushed and a send operation is
|
||||
performed when there is an outgoing DNS message. 3. **Bumping Active
|
||||
Streams Processing Limit**: - The total number of active streams
|
||||
has been increased to 60% of the total streams limit.
|
||||
|
||||
These changes collectively enhance the performance and reliability of
|
||||
the DoH implementation, making it more efficient and robust for
|
||||
handling high-load scenarios, particularly noticeable in long runs (>=
|
||||
1h) of `stress:long:rpz:doh+udp:linux:*` tests. It improves perf. for
|
||||
tests for BIND 9.18, but it likely will have a positive but less
|
||||
pronounced effect on newer versions as well.
|
||||
|
||||
In essence, the merge request fixes three bottlenecks stacked upon
|
||||
each other.
|
||||
|
||||
*It is a logical continuation of the merge requests !10109.* !10109,
|
||||
unfortunately, did not completely [address the performance drop in
|
||||
9.18](https://gitlab.isc.org/isc-projects/bind9/-/pipelines/221545)
|
||||
for longer runs of the stress test. This merge request [addresses
|
||||
that](https://gitlab.isc.org/isc-projects/bind9/-/pipelines/223661).
|
||||
|
||||
**P.S.**
|
||||
|
||||
The origin of the fixes is, in fact, the branch in !10193. So this MR
|
||||
is a ... *forward port* of them. :gl:`!10192`
|
||||
|
||||
- Post [CVE-2024-12705] Performance Drop Fixes. ``3033d127d2``
|
||||
|
||||
This merge request fixes a [performance
|
||||
drop](https://gitlab.isc.org/isc-projects/bind9/-/pipelines/216728)
|
||||
after merging the fixes for #4795, in particular in 9.18.
|
||||
|
||||
The MR [fixes the
|
||||
problem](https://gitlab.isc.org/isc-projects/bind9/-/pipelines/219825)
|
||||
without affecting performance for the newer versions, in particular
|
||||
for [the development version](https://gitlab.isc.org/isc-projects/bind
|
||||
9/-/pipelines/220619). :gl:`!10109`
|
||||
|
||||
- Remove 'target' from dns_adb. ``764eb65cf6``
|
||||
|
||||
When a server name turns out to be a CNAME or DNAME, the ADB does not
|
||||
use it, but the `dns_adbname` structure still stored a copy of the
|
||||
target name. This is unnecessary and the code has been removed.
|
||||
:gl:`!10149`
|
||||
|
||||
- Simplify some dns_name API calls. ``e16560a650``
|
||||
|
||||
Several functions in the `dns_name` module have had parameters
|
||||
removed, that were rarely or never used: - `dns_name_fromtext()` and
|
||||
`dns_name_concatenate()` no longer take a target buffer. -
|
||||
`dns_name_towire()` no longer takes a compression offset pointer; this
|
||||
is now part of the compression context. - `dns_name_towire()` with a
|
||||
`NULL` compression context will copy name data directly into a buffer
|
||||
with no processing. :gl:`!10152`
|
||||
|
||||
- Sync the TSAN CC, CFLAGS and LDFLAGS in the respdiff:tsan job.
|
||||
``22b5442722``
|
||||
|
||||
:gl:`!10209`
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ zone <string> [ <class> ] {
|
||||
notify-source ( <ipv4_address> | * );
|
||||
notify-source-v6 ( <ipv6_address> | * );
|
||||
primaries [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
|
||||
provide-zoneversion <boolean>;
|
||||
request-expire <boolean>;
|
||||
request-ixfr <boolean>;
|
||||
request-ixfr-max-diffs <integer>;
|
||||
|
||||
@@ -226,6 +226,7 @@ options {
|
||||
preferred-glue <string>;
|
||||
prefetch <integer> [ <integer> ];
|
||||
provide-ixfr <boolean>;
|
||||
provide-zoneversion <boolean>;
|
||||
qname-minimization ( strict | relaxed | disabled | off );
|
||||
query-source [ address ] ( <ipv4_address> | * | none );
|
||||
query-source-v6 [ address ] ( <ipv6_address> | * | none );
|
||||
@@ -254,6 +255,7 @@ options {
|
||||
request-ixfr <boolean>;
|
||||
request-ixfr-max-diffs <integer>;
|
||||
request-nsid <boolean>;
|
||||
request-zoneversion <boolean>;
|
||||
require-server-cookie <boolean>;
|
||||
resolver-query-timeout <integer>;
|
||||
resolver-use-dns64 <boolean>;
|
||||
@@ -343,6 +345,7 @@ server <netprefix> {
|
||||
request-ixfr <boolean>;
|
||||
request-ixfr-max-diffs <integer>;
|
||||
request-nsid <boolean>;
|
||||
request-zoneversion <boolean>;
|
||||
require-cookie <boolean>;
|
||||
send-cookie <boolean>;
|
||||
tcp-keepalive <boolean>;
|
||||
@@ -509,6 +512,7 @@ view <string> [ <class> ] {
|
||||
preferred-glue <string>;
|
||||
prefetch <integer> [ <integer> ];
|
||||
provide-ixfr <boolean>;
|
||||
provide-zoneversion <boolean>;
|
||||
qname-minimization ( strict | relaxed | disabled | off );
|
||||
query-source [ address ] ( <ipv4_address> | * | none );
|
||||
query-source-v6 [ address ] ( <ipv6_address> | * | none );
|
||||
@@ -534,6 +538,7 @@ view <string> [ <class> ] {
|
||||
request-ixfr <boolean>;
|
||||
request-ixfr-max-diffs <integer>;
|
||||
request-nsid <boolean>;
|
||||
request-zoneversion <boolean>;
|
||||
require-server-cookie <boolean>;
|
||||
resolver-query-timeout <integer>;
|
||||
resolver-use-dns64 <boolean>;
|
||||
@@ -561,6 +566,7 @@ view <string> [ <class> ] {
|
||||
request-ixfr <boolean>;
|
||||
request-ixfr-max-diffs <integer>;
|
||||
request-nsid <boolean>;
|
||||
request-zoneversion <boolean>;
|
||||
require-cookie <boolean>;
|
||||
send-cookie <boolean>;
|
||||
tcp-keepalive <boolean>;
|
||||
|
||||
@@ -51,6 +51,7 @@ zone <string> [ <class> ] {
|
||||
parental-agents [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
|
||||
parental-source ( <ipv4_address> | * );
|
||||
parental-source-v6 ( <ipv6_address> | * );
|
||||
provide-zoneversion <boolean>;
|
||||
send-report-channel <string>;
|
||||
serial-update-method ( date | increment | unixtime );
|
||||
sig-signing-nodes <integer>;
|
||||
|
||||
@@ -50,6 +50,7 @@ zone <string> [ <class> ] {
|
||||
parental-source ( <ipv4_address> | * );
|
||||
parental-source-v6 ( <ipv6_address> | * );
|
||||
primaries [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
|
||||
provide-zoneversion <boolean>;
|
||||
request-expire <boolean>;
|
||||
request-ixfr <boolean>;
|
||||
request-ixfr-max-diffs <integer>;
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
.. 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.
|
||||
|
||||
Notes for BIND 9.21.6
|
||||
---------------------
|
||||
|
||||
New Features
|
||||
~~~~~~~~~~~~
|
||||
|
||||
- Implement the :any:`min-transfer-rate-in` configuration option.
|
||||
|
||||
A new option :any:`min-transfer-rate-in` has been added
|
||||
to the view and zone configurations. It can abort incoming zone
|
||||
transfers that run very slowly due to network-related issues, for
|
||||
example. The default value is 10240 bytes in five minutes.
|
||||
:gl:`#3914`
|
||||
|
||||
- Add HTTPS record query to :iscman:`host` command line tool.
|
||||
|
||||
The :iscman:`host` command was extended to also query for the HTTPS RR
|
||||
type by default.
|
||||
|
||||
- Implement :any:`sig0key-checks-limit` and :any:`sig0message-checks-limit`.
|
||||
|
||||
Previously, a hard-coded limitation of a maximum of two key or message
|
||||
verification checks was introduced when checking a message's ``SIG(0)``
|
||||
signature, to protect against possible DoS
|
||||
attacks. Two as a maximum was chosen so that more than a
|
||||
single key should only be required during key rotations, and in that
|
||||
case two keys are enough. It later became apparent that there are
|
||||
other use cases where even more keys are required; see the related GitLab issue for examples.
|
||||
|
||||
This change introduces two new configuration options for the views:
|
||||
:any:`sig0key-checks-limit` and :any:`sig0message-checks-limit`. They define
|
||||
how many keys can be checked to find a matching key, and
|
||||
how many message verifications are allowed to take place once a
|
||||
matching key has been found. The former provides
|
||||
slightly less "expensive" key parsing operations and defaults to
|
||||
16. The latter protects against expensive
|
||||
cryptographic operations when there are keys with colliding tags and
|
||||
algorithm numbers; the default is 2. :gl:`#5050`
|
||||
|
||||
Feature Changes
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
- Drop malformed notify messages early instead of decompressing them.
|
||||
|
||||
The DNS header shows whether a message has multiple questions or invalid
|
||||
NOTIFY sections. :iscman:`named` can now drop these messages early, right after parsing
|
||||
the question, to match :rfc:`9619` for multi-question messages and
|
||||
Unbound's handling of NOTIFY. Questions are still parsed to be included
|
||||
in BIND's FORMERR response.
|
||||
|
||||
Add ``drop_msg_early()`` function to check for these conditions:
|
||||
|
||||
- Messages with more than one question, as required by :rfc:`9619`
|
||||
- NOTIFY query messages containing answer sections (like Unbound)
|
||||
|
||||
:gl:`#5158`
|
||||
|
||||
- Reduce memory used to store DNS names.
|
||||
|
||||
The memory used to internally store the DNS names has been reduced
|
||||
by no longer caching certain fields from an internal data structure.
|
||||
|
||||
|
||||
Bug Fixes
|
||||
~~~~~~~~~
|
||||
|
||||
- Fix :any:`dual-stack-servers` configuration option.
|
||||
|
||||
The :any:`dual-stack-servers` configuration option was not working as
|
||||
expected; the specified servers were not being used when they should
|
||||
have been, leading to resolution failures. This has been fixed.
|
||||
:gl:`#5019`
|
||||
|
||||
- Fix a data race causing a permanent active client increase.
|
||||
|
||||
Previously, a data race could cause a newly created fetch context for
|
||||
a new client to be used before it had been fully initialized, which
|
||||
would cause the query to become stuck; queries for the same data would
|
||||
be either paused indefinitely or dropped because of the
|
||||
:any:`clients-per-query` limit. This has been fixed. :gl:`#5053`
|
||||
|
||||
- Fix deferred validation of unsigned DS and DNSKEY records.
|
||||
|
||||
When processing a query with the "checking disabled" bit set (CD=1),
|
||||
:iscman:`named` stores the invalidated result in the cache, marked "pending".
|
||||
When the same query is sent with CD=0, the cached data is validated
|
||||
and either accepted as an answer, or ejected from the cache as
|
||||
invalid. This deferred validation was not attempted for DS and DNSKEY
|
||||
records if they had no cached signatures, causing spurious validation
|
||||
failures. The deferred validation is now completed in this scenario.
|
||||
|
||||
Also, if deferred validation fails, the data is now re-queried to find
|
||||
out whether the zone has been corrected since the invalid data was
|
||||
cached. :gl:`#5066`
|
||||
|
||||
- Fix RPZ race condition during a reconfiguration.
|
||||
|
||||
With RPZ in use, :iscman:`named` could terminate unexpectedly because of a
|
||||
race condition when a reconfiguration command was received using
|
||||
:iscman:`rndc`. This has been fixed. :gl:`#5146`
|
||||
|
||||
- "CNAME and other data check" not applied to all types.
|
||||
|
||||
An incorrect optimization caused "CNAME and other data" errors not to
|
||||
be detected if certain types were at the same node as a CNAME. This
|
||||
has been fixed. :gl:`#5150`
|
||||
|
||||
- Use named Service Parameter Keys (``SvcParamKeys``) by default.
|
||||
|
||||
When converting SVCB records to text representation, :iscman:`named` now uses
|
||||
named ``SvcParamKeys`` values unless backward-compatible mode is
|
||||
activated. In that case, values which were not defined initially
|
||||
in :rfc:`9460` and were added later (see [1]) are converted to opaque
|
||||
"keyNNNN" syntax, e.g. "key7" instead of "dohpath".
|
||||
|
||||
Also a new ``+[no]svcparamkeycompat`` option is implemented for :iscman:`dig`,
|
||||
which enables the backward-compatible mode and uses the opaque syntax,
|
||||
if required for interoperability with other software or scripts. By
|
||||
default, the compatibility mode is disabled.
|
||||
|
||||
[1] https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml
|
||||
:gl:`#5156`
|
||||
|
||||
- Relax private DNSKEY and RRSIG constraints.
|
||||
|
||||
DNSKEY, KEY, RRSIG, and SIG constraints have been relaxed to allow
|
||||
empty key and signature material after the algorithm identifier for
|
||||
PRIVATEOID and PRIVATEDNS. It is arguable whether this falls within
|
||||
the expected use of these types, as no key material is shared and the
|
||||
signatures are ineffective, but these are private algorithms and they
|
||||
can be totally insecure. :gl:`#5167`
|
||||
|
||||
- Remove NSEC/DS/NSEC3 RRSIG check from ``dns_message_parse()``.
|
||||
|
||||
Previously, when parsing responses, :iscman:`named` incorrectly rejected
|
||||
responses without matching RRSIG records for NSEC/DS/NSEC3 records in
|
||||
the authority section. This rejection, if appropriate, should have
|
||||
been left for the validator to determine and has been fixed.
|
||||
:gl:`#5185`
|
||||
|
||||
- Fix TTL issue with ANY queries processed through RPZ "passthru".
|
||||
|
||||
Answers to an "ANY" query which were processed by the RPZ "passthru"
|
||||
policy had the response-policy's ``max-policy-ttl`` value unexpectedly
|
||||
applied. This has been fixed. :gl:`#5187`
|
||||
|
||||
- :iscman:`dnssec-signzone` needs to check for a NULL key when setting offline.
|
||||
|
||||
:iscman:`dnssec-signzone` could dereference a NULL key pointer when resigning
|
||||
a zone. This has been fixed. :gl:`#5192`
|
||||
|
||||
- Fix a bug in the statistics channel when querying zone transfer
|
||||
information.
|
||||
|
||||
When querying zone transfer information from the statistics channel,
|
||||
there was a rare possibility that :iscman:`named` could terminate unexpectedly
|
||||
if a zone transfer was in a state when transferring from all the
|
||||
available primary servers had failed earlier. This has been fixed.
|
||||
:gl:`#5198`
|
||||
|
||||
- Fix assertion failure when dumping recursing clients.
|
||||
|
||||
Previously, if a new counter was added to the hash table while dumping
|
||||
recursing clients via the :option:`rndc recursing` command, and
|
||||
:any:`fetches-per-zone` was enabled, an assertion failure could occur. This
|
||||
has been fixed. :gl:`#5200`
|
||||
|
||||
- Dump the active resolver fetches from ``dns_resolver_dumpfetches()``
|
||||
|
||||
Previously, active resolver fetches were only dumped when the
|
||||
:any:`fetches-per-zone` configuration option was enabled. Now, active
|
||||
resolver fetches are dumped along with the number of
|
||||
:any:`clients-per-query` counters per resolver fetch.
|
||||
|
||||
|
||||
@@ -136,7 +136,6 @@ libdns_la_HEADERS = \
|
||||
include/dns/view.h \
|
||||
include/dns/xfrin.h \
|
||||
include/dns/zone.h \
|
||||
include/dns/zonekey.h \
|
||||
include/dns/zoneverify.h \
|
||||
include/dns/zt.h
|
||||
|
||||
@@ -255,7 +254,6 @@ libdns_la_SOURCES = \
|
||||
zone.c \
|
||||
zone_p.h \
|
||||
zoneverify.c \
|
||||
zonekey.c \
|
||||
zt.c
|
||||
|
||||
if HAVE_GSSAPI
|
||||
|
||||
+7
-7
@@ -567,7 +567,7 @@ import_rdataset(dns_adbname_t *adbname, dns_rdataset_t *rdataset,
|
||||
rdataset->ttl = ttlclamp(rdataset->ttl);
|
||||
}
|
||||
|
||||
REQUIRE(rdtype == dns_rdatatype_a || rdtype == dns_rdatatype_aaaa);
|
||||
REQUIRE(dns_rdatatype_isaddr(rdtype));
|
||||
|
||||
for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
|
||||
result = dns_rdataset_next(rdataset))
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -2557,7 +2557,7 @@ dbfind_name(dns_adbname_t *adbname, isc_stdtime_t now, dns_rdatatype_t rdtype) {
|
||||
adb = adbname->adb;
|
||||
|
||||
REQUIRE(DNS_ADB_VALID(adb));
|
||||
REQUIRE(rdtype == dns_rdatatype_a || rdtype == dns_rdatatype_aaaa);
|
||||
REQUIRE(dns_rdatatype_isaddr(rdtype));
|
||||
|
||||
fname = dns_fixedname_initname(&foundname);
|
||||
dns_rdataset_init(&rdataset);
|
||||
@@ -2909,6 +2909,7 @@ fetch_name(dns_adbname_t *adbname, bool start_at_zone, bool no_validation,
|
||||
* createfetch to find deepest cached name when we're providing
|
||||
* domain and nameservers.
|
||||
*/
|
||||
dns_adbname_ref(adbname);
|
||||
result = dns_resolver_createfetch(
|
||||
adb->res, adbname->name, type, name, nameservers, NULL, NULL, 0,
|
||||
options, depth, qc, gqc, isc_loop(), fetch_callback, adbname,
|
||||
@@ -2916,11 +2917,10 @@ fetch_name(dns_adbname_t *adbname, bool start_at_zone, bool no_validation,
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
DP(ENTER_LEVEL, "fetch_name: createfetch failed with %s",
|
||||
isc_result_totext(result));
|
||||
dns_adbname_unref(adbname);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
dns_adbname_ref(adbname);
|
||||
|
||||
if (type == dns_rdatatype_a) {
|
||||
adbname->fetch_a = fetch;
|
||||
inc_resstats(adb, dns_resstatscounter_gluefetchv4);
|
||||
|
||||
+1
-2
@@ -1516,8 +1516,7 @@ catz_process_primaries(dns_catz_zone_t *catz, dns_ipkeylist_t *ipkl,
|
||||
}
|
||||
/* else - 'simple' case - without labels */
|
||||
|
||||
if (value->type != dns_rdatatype_a && value->type != dns_rdatatype_aaaa)
|
||||
{
|
||||
if (!dns_rdatatype_isaddr(value->type)) {
|
||||
return ISC_R_FAILURE;
|
||||
}
|
||||
|
||||
|
||||
@@ -1200,3 +1200,14 @@ dns__db_logtoomanyrecords(dns_db_t *db, const dns_name_t *name,
|
||||
(db->attributes & DNS_DBATTR_CACHE) != 0 ? "cache" : "zone",
|
||||
isc_result_totext(DNS_R_TOOMANYRECORDS), limit);
|
||||
}
|
||||
|
||||
isc_result_t
|
||||
dns_db_getzoneversion(dns_db_t *db, isc_buffer_t *b) {
|
||||
REQUIRE(db != NULL);
|
||||
REQUIRE(b != NULL);
|
||||
|
||||
if (db->methods->getzoneversion != NULL) {
|
||||
return (db->methods->getzoneversion)(db, b);
|
||||
}
|
||||
return ISC_R_NOTIMPLEMENTED;
|
||||
}
|
||||
|
||||
+53
-12
@@ -1101,6 +1101,41 @@ dns_dnssec_signs(dns_rdata_t *rdata, const dns_name_t *name,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
dns_dnssec_iszonekey(dns_rdata_dnskey_t *key) {
|
||||
return (key->flags & DNS_KEYFLAG_OWNERMASK) == DNS_KEYOWNER_ZONE &&
|
||||
(key->flags & DNS_KEYTYPE_NOAUTH) == 0 &&
|
||||
(key->protocol == DNS_KEYPROTO_DNSSEC ||
|
||||
key->protocol == DNS_KEYPROTO_ANY);
|
||||
}
|
||||
|
||||
bool
|
||||
dns_dnssec_haszonekey(dns_rdataset_t *keyset) {
|
||||
isc_result_t result;
|
||||
|
||||
REQUIRE(keyset != NULL);
|
||||
|
||||
if (keyset->type != dns_rdatatype_dnskey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (result = dns_rdataset_first(keyset); result == ISC_R_SUCCESS;
|
||||
result = dns_rdataset_next(keyset))
|
||||
{
|
||||
dns_rdata_t rdata = DNS_RDATA_INIT;
|
||||
dns_rdata_dnskey_t key;
|
||||
|
||||
dns_rdataset_current(keyset, &rdata);
|
||||
dns_rdata_tostruct(&rdata, &key, NULL); /* can't fail */
|
||||
|
||||
if (dns_dnssec_iszonekey(&key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
dns_dnsseckey_create(isc_mem_t *mctx, dst_key_t **dstkey,
|
||||
dns_dnsseckey_t **dkp) {
|
||||
@@ -1428,29 +1463,35 @@ addkey(dns_dnsseckeylist_t *keylist, dst_key_t **newkey, bool savekeys,
|
||||
|
||||
if (key != NULL) {
|
||||
/*
|
||||
* Found a match. If the old key was only public and the
|
||||
* new key is private, replace the old one; otherwise
|
||||
* leave it. But either way, mark the key as having
|
||||
* been found in the zone.
|
||||
* Found a match. If we already had a private key, then
|
||||
* the new key can't be an improvement. If the existing
|
||||
* key was public-only but the new key is too, then it's
|
||||
* still not an improvement. Mark the old key as having
|
||||
* been found in the zone and stop.
|
||||
*/
|
||||
if (dst_key_isprivate(key->key)) {
|
||||
dst_key_free(newkey);
|
||||
} else if (dst_key_isprivate(*newkey)) {
|
||||
dst_key_free(&key->key);
|
||||
key->key = *newkey;
|
||||
if (dst_key_isprivate(key->key) || !dst_key_isprivate(*newkey))
|
||||
{
|
||||
key->source = dns_keysource_zoneapex;
|
||||
return;
|
||||
}
|
||||
|
||||
key->source = dns_keysource_zoneapex;
|
||||
return;
|
||||
/*
|
||||
* However, if the old key was public-only, and the new key
|
||||
* is private, then we're throwing away the old key.
|
||||
*/
|
||||
dst_key_free(&key->key);
|
||||
ISC_LIST_UNLINK(*keylist, key, link);
|
||||
dns_dnsseckey_destroy(mctx, &key);
|
||||
}
|
||||
|
||||
/* Store the new key. */
|
||||
dns_dnsseckey_create(mctx, newkey, &key);
|
||||
key->source = dns_keysource_zoneapex;
|
||||
key->pubkey = pubkey_only;
|
||||
if (key->legacy || savekeys) {
|
||||
key->force_publish = true;
|
||||
key->force_sign = dst_key_isprivate(key->key);
|
||||
}
|
||||
key->source = dns_keysource_zoneapex;
|
||||
ISC_LIST_APPEND(*keylist, key, link);
|
||||
*newkey = NULL;
|
||||
}
|
||||
|
||||
+8
-19
@@ -162,8 +162,7 @@ computeid(dst_key_t *key);
|
||||
static isc_result_t
|
||||
frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
|
||||
unsigned int protocol, dns_rdataclass_t rdclass,
|
||||
isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata,
|
||||
dst_key_t **keyp);
|
||||
isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp);
|
||||
|
||||
static isc_result_t
|
||||
algorithm_status(unsigned int alg);
|
||||
@@ -721,13 +720,6 @@ dst_key_todns(const dst_key_t *key, isc_buffer_t *target) {
|
||||
isc_result_t
|
||||
dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass,
|
||||
isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) {
|
||||
return dst_key_fromdns_ex(name, rdclass, source, mctx, false, keyp);
|
||||
}
|
||||
|
||||
isc_result_t
|
||||
dst_key_fromdns_ex(const dns_name_t *name, dns_rdataclass_t rdclass,
|
||||
isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata,
|
||||
dst_key_t **keyp) {
|
||||
uint8_t alg, proto;
|
||||
uint32_t flags, extflags;
|
||||
dst_key_t *key = NULL;
|
||||
@@ -756,7 +748,7 @@ dst_key_fromdns_ex(const dns_name_t *name, dns_rdataclass_t rdclass,
|
||||
}
|
||||
|
||||
result = frombuffer(name, alg, flags, proto, rdclass, source, mctx,
|
||||
no_rdata, &key);
|
||||
&key);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
@@ -775,7 +767,7 @@ dst_key_frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
|
||||
isc_result_t result;
|
||||
|
||||
result = frombuffer(name, alg, flags, protocol, rdclass, source, mctx,
|
||||
false, &key);
|
||||
&key);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
@@ -2267,8 +2259,7 @@ computeid(dst_key_t *key) {
|
||||
static isc_result_t
|
||||
frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
|
||||
unsigned int protocol, dns_rdataclass_t rdclass,
|
||||
isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata,
|
||||
dst_key_t **keyp) {
|
||||
isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) {
|
||||
dst_key_t *key;
|
||||
isc_result_t ret;
|
||||
|
||||
@@ -2290,12 +2281,10 @@ frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
|
||||
return DST_R_UNSUPPORTEDALG;
|
||||
}
|
||||
|
||||
if (!no_rdata) {
|
||||
ret = key->func->fromdns(key, source);
|
||||
if (ret != ISC_R_SUCCESS) {
|
||||
dst_key_free(&key);
|
||||
return ret;
|
||||
}
|
||||
ret = key->func->fromdns(key, source);
|
||||
if (ret != ISC_R_SUCCESS) {
|
||||
dst_key_free(&key);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -182,6 +182,7 @@ typedef struct dns_dbmethods {
|
||||
dns_name_t *name);
|
||||
void (*setmaxrrperset)(dns_db_t *db, uint32_t value);
|
||||
void (*setmaxtypepername)(dns_db_t *db, uint32_t value);
|
||||
isc_result_t (*getzoneversion)(dns_db_t *db, isc_buffer_t *b);
|
||||
} dns_dbmethods_t;
|
||||
|
||||
typedef isc_result_t (*dns_dbcreatefunc_t)(isc_mem_t *mctx,
|
||||
@@ -1805,3 +1806,21 @@ dns_db_setmaxtypepername(dns_db_t *db, uint32_t value);
|
||||
* stored at a given node, then any subsequent attempt to add an rdataset
|
||||
* with a new RR type will return ISC_R_TOOMANYRECORDS.
|
||||
*/
|
||||
|
||||
isc_result_t
|
||||
dns_db_getzoneversion(dns_db_t *db, isc_buffer_t *b);
|
||||
/*%<
|
||||
* Provides a database specific EDNS ZONEVERSION option.
|
||||
*
|
||||
* Requires:
|
||||
* \li 'db' is a valid database
|
||||
* \li 'b' is a valid buffer
|
||||
*
|
||||
* Returns:
|
||||
* \li ISC_R_SUCCESS when it has populated the buffer with the ZONEVERSION
|
||||
* response (maybe empty implying no ZONEVERSION to be returned).
|
||||
* \li ISC_R_NOSPACE if the buffer is too small.
|
||||
* \li ISC_R_NOTIMPLEMENTED if there is not a database specific
|
||||
* ZONEVERSION
|
||||
* \li ISC_R_FAILURE other failures
|
||||
*/
|
||||
|
||||
@@ -242,6 +242,24 @@ dns_dnssec_signs(dns_rdata_t *rdata, const dns_name_t *name,
|
||||
* rrset. dns_dnssec_signs() works on any rrset.
|
||||
*/
|
||||
|
||||
bool
|
||||
dns_dnssec_iszonekey(dns_rdata_dnskey_t *key);
|
||||
/*%<
|
||||
* Verify that 'key' is a DNSSEC key with the DNS_KEYOWNER_ZONE flag set.
|
||||
*
|
||||
* Requires:
|
||||
*\li 'key' is not NULL.
|
||||
*/
|
||||
|
||||
bool
|
||||
dns_dnssec_haszonekey(dns_rdataset_t *keyset);
|
||||
/*%<
|
||||
* Verify that 'keyset' includes at least one zone key.
|
||||
*
|
||||
* Requires:
|
||||
*\li 'keyset' is not NULL.
|
||||
*/
|
||||
|
||||
void
|
||||
dns_dnsseckey_create(isc_mem_t *mctx, dst_key_t **dstkey,
|
||||
dns_dnsseckey_t **dkp);
|
||||
|
||||
@@ -102,7 +102,6 @@
|
||||
#define DNS_MESSAGEEXTFLAG_DO 0x8000U
|
||||
|
||||
/*%< EDNS0 extended OPT codes */
|
||||
|
||||
#define DNS_OPT_LLQ 1 /*%< LLQ opt code */
|
||||
#define DNS_OPT_UL 2 /*%< UL opt code */
|
||||
#define DNS_OPT_NSID 3 /*%< NSID opt code */
|
||||
@@ -129,7 +128,7 @@
|
||||
* options we know about. Extended DNS Errors may occur multiple times, see
|
||||
* DNS_EDE_MAX_ERRORS.
|
||||
*/
|
||||
#define DNS_EDNSOPTIONS 8 + DNS_EDE_MAX_ERRORS
|
||||
#define DNS_EDNSOPTIONS 9 + DNS_EDE_MAX_ERRORS
|
||||
|
||||
#define DNS_MESSAGE_REPLYPRESERVE (DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_CD)
|
||||
#define DNS_MESSAGEEXTFLAG_REPLYPRESERVE (DNS_MESSAGEEXTFLAG_DO)
|
||||
|
||||
@@ -115,6 +115,12 @@ dns_peer_setrequestnsid(dns_peer_t *peer, bool newval);
|
||||
isc_result_t
|
||||
dns_peer_getrequestnsid(dns_peer_t *peer, bool *retval);
|
||||
|
||||
isc_result_t
|
||||
dns_peer_setrequestzoneversion(dns_peer_t *peer, bool newval);
|
||||
|
||||
isc_result_t
|
||||
dns_peer_getrequestzoneversion(dns_peer_t *peer, bool *retval);
|
||||
|
||||
isc_result_t
|
||||
dns_peer_setsendcookie(dns_peer_t *peer, bool newval);
|
||||
|
||||
|
||||
+190
-117
@@ -113,6 +113,36 @@ struct dns_rdata {
|
||||
ISC_LINK(dns_rdata_t) link;
|
||||
};
|
||||
|
||||
/*%
|
||||
* Rdatatype attributes.
|
||||
*/
|
||||
enum {
|
||||
/*% only one may exist for a name */
|
||||
DNS_RDATATYPEATTR_SINGLETON = 1 << 0,
|
||||
/*% requires no other data be present */
|
||||
DNS_RDATATYPEATTR_EXCLUSIVE = 1 << 1,
|
||||
/*% Is a meta type */
|
||||
DNS_RDATATYPEATTR_META = 1 << 2,
|
||||
/*% Is a DNSSEC type, like RRSIG or NSEC */
|
||||
DNS_RDATATYPEATTR_DNSSEC = 1 << 3,
|
||||
/*% Is a zone cut authority type */
|
||||
DNS_RDATATYPEATTR_ZONECUTAUTH = 1 << 4,
|
||||
/*% Is reserved (unusable) */
|
||||
DNS_RDATATYPEATTR_RESERVED = 1 << 5,
|
||||
/*% Is an unknown type */
|
||||
DNS_RDATATYPEATTR_UNKNOWN = 1 << 6,
|
||||
/*% Is META, and can only be in a question section */
|
||||
DNS_RDATATYPEATTR_QUESTIONONLY = 1 << 7,
|
||||
/*% Is META, and can NOT be in a question section */
|
||||
DNS_RDATATYPEATTR_NOTQUESTION = 1 << 8,
|
||||
/*% Is present at zone cuts in the parent, not the child */
|
||||
DNS_RDATATYPEATTR_ATPARENT = 1 << 9,
|
||||
/*% Can exist along side a CNAME */
|
||||
DNS_RDATATYPEATTR_ATCNAME = 1 << 10,
|
||||
/*% Follow additional */
|
||||
DNS_RDATATYPEATTR_FOLLOWADDITIONAL = 1 << 11,
|
||||
};
|
||||
|
||||
#define DNS_RDATA_INIT \
|
||||
{ \
|
||||
.data = NULL, \
|
||||
@@ -530,16 +560,28 @@ dns_rdata_freestruct(void *source);
|
||||
* dns_rdata_tostruct().
|
||||
*/
|
||||
|
||||
bool
|
||||
dns_rdatatype_ismeta(dns_rdatatype_t type);
|
||||
unsigned int
|
||||
dns_rdatatype_attributes(dns_rdatatype_t rdtype);
|
||||
/*%<
|
||||
* Return attributes for the given type.
|
||||
*
|
||||
* Requires:
|
||||
*\li 'rdtype' are known.
|
||||
*
|
||||
* Returns:
|
||||
*\li a bitmask of the rdatatype attribute flags, defined above.
|
||||
*/
|
||||
|
||||
/*%
|
||||
* Return true iff the rdata type 'type' is a meta-type
|
||||
* like ANY or AXFR.
|
||||
*/
|
||||
static inline bool
|
||||
dns_rdatatype_ismeta(dns_rdatatype_t type) {
|
||||
return (dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_META) != 0;
|
||||
}
|
||||
|
||||
bool
|
||||
dns_rdatatype_issingleton(dns_rdatatype_t type);
|
||||
/*%<
|
||||
/*%
|
||||
* Return true iff the rdata type 'type' is a singleton type,
|
||||
* like CNAME or SOA.
|
||||
*
|
||||
@@ -547,34 +589,108 @@ dns_rdatatype_issingleton(dns_rdatatype_t type);
|
||||
* \li 'type' is a valid rdata type.
|
||||
*
|
||||
*/
|
||||
static inline bool
|
||||
dns_rdatatype_issingleton(dns_rdatatype_t type) {
|
||||
return (dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_SINGLETON) !=
|
||||
0;
|
||||
}
|
||||
|
||||
bool
|
||||
dns_rdataclass_ismeta(dns_rdataclass_t rdclass);
|
||||
/*%<
|
||||
* Return true iff the rdata class 'rdclass' is a meta-class
|
||||
* like ANY or NONE.
|
||||
/*%
|
||||
* Return true iff rdata of type 'type' can not appear in the question
|
||||
* section of a properly formatted message.
|
||||
*
|
||||
* Requires:
|
||||
* \li 'type' is a valid rdata type.
|
||||
*
|
||||
*/
|
||||
static inline bool
|
||||
dns_rdatatype_notquestion(dns_rdatatype_t type) {
|
||||
return (dns_rdatatype_attributes(type) &
|
||||
DNS_RDATATYPEATTR_NOTQUESTION) != 0;
|
||||
}
|
||||
|
||||
bool
|
||||
dns_rdatatype_isdnssec(dns_rdatatype_t type);
|
||||
/*%<
|
||||
/*%
|
||||
* Return true iff rdata of type 'type' can only appear in the question
|
||||
* section of a properly formatted message.
|
||||
*
|
||||
* Requires:
|
||||
* \li 'type' is a valid rdata type.
|
||||
*
|
||||
*/
|
||||
static inline bool
|
||||
dns_rdatatype_questiononly(dns_rdatatype_t type) {
|
||||
return (dns_rdatatype_attributes(type) &
|
||||
DNS_RDATATYPEATTR_QUESTIONONLY) != 0;
|
||||
}
|
||||
|
||||
/*%
|
||||
* Return true iff rdata of type 'type' can appear beside a cname.
|
||||
*
|
||||
* Requires:
|
||||
* \li 'type' is a valid rdata type.
|
||||
*
|
||||
*/
|
||||
static inline bool
|
||||
dns_rdatatype_atcname(dns_rdatatype_t type) {
|
||||
return (dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_ATCNAME) !=
|
||||
0;
|
||||
}
|
||||
|
||||
/*%
|
||||
* Return true iff rdata of type 'type' should appear at the parent of
|
||||
* a zone cut.
|
||||
*
|
||||
* Requires:
|
||||
* \li 'type' is a valid rdata type.
|
||||
*
|
||||
*/
|
||||
static inline bool
|
||||
dns_rdatatype_atparent(dns_rdatatype_t type) {
|
||||
return (dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_ATPARENT) !=
|
||||
0;
|
||||
}
|
||||
|
||||
/*%
|
||||
* Return true if adding a record of type 'type' to the ADDITIONAL section
|
||||
* of a message can itself trigger the addition of still more data to the
|
||||
* additional section.
|
||||
*
|
||||
* (For example: adding SRV to the ADDITIONAL section may trigger
|
||||
* the addition of address records associated with that SRV.)
|
||||
*
|
||||
* Requires:
|
||||
* \li 'type' is a valid rdata type.
|
||||
*
|
||||
*/
|
||||
static inline bool
|
||||
dns_rdatatype_followadditional(dns_rdatatype_t type) {
|
||||
return (dns_rdatatype_attributes(type) &
|
||||
DNS_RDATATYPEATTR_FOLLOWADDITIONAL) != 0;
|
||||
}
|
||||
|
||||
/*%
|
||||
* Return true iff 'type' is one of the DNSSEC
|
||||
* rdata types that may exist alongside a CNAME record.
|
||||
*
|
||||
* Requires:
|
||||
* \li 'type' is a valid rdata type.
|
||||
*/
|
||||
static inline bool
|
||||
dns_rdatatype_isdnssec(dns_rdatatype_t type) {
|
||||
return (dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_DNSSEC) != 0;
|
||||
}
|
||||
|
||||
bool
|
||||
dns_rdatatype_iskeymaterial(dns_rdatatype_t type);
|
||||
/*%<
|
||||
/*%
|
||||
* Return true iff the rdata type 'type' is a DNSSEC key
|
||||
* related type, like DNSKEY, CDNSKEY, or CDS.
|
||||
*/
|
||||
static inline bool
|
||||
dns_rdatatype_iskeymaterial(dns_rdatatype_t type) {
|
||||
return type == dns_rdatatype_dnskey || type == dns_rdatatype_cdnskey ||
|
||||
type == dns_rdatatype_cds;
|
||||
}
|
||||
|
||||
bool
|
||||
dns_rdatatype_iszonecutauth(dns_rdatatype_t type);
|
||||
/*%<
|
||||
/*%
|
||||
* Return true iff rdata of type 'type' is considered authoritative
|
||||
* data (not glue) in the NSEC chain when it occurs in the parent zone
|
||||
* at a zone cut.
|
||||
@@ -583,16 +699,68 @@ dns_rdatatype_iszonecutauth(dns_rdatatype_t type);
|
||||
* \li 'type' is a valid rdata type.
|
||||
*
|
||||
*/
|
||||
static inline bool
|
||||
dns_rdatatype_iszonecutauth(dns_rdatatype_t type) {
|
||||
return (dns_rdatatype_attributes(type) &
|
||||
DNS_RDATATYPEATTR_ZONECUTAUTH) != 0;
|
||||
}
|
||||
|
||||
bool
|
||||
dns_rdatatype_isknown(dns_rdatatype_t type);
|
||||
/*%<
|
||||
/*%
|
||||
* Return true iff the rdata type 'type' is known.
|
||||
*
|
||||
* Requires:
|
||||
* \li 'type' is a valid rdata type.
|
||||
*
|
||||
*/
|
||||
static inline bool
|
||||
dns_rdatatype_isknown(dns_rdatatype_t type) {
|
||||
return (dns_rdatatype_attributes(type) & DNS_RDATATYPEATTR_UNKNOWN) ==
|
||||
0;
|
||||
}
|
||||
|
||||
/*%
|
||||
* Return true iff a query for the rdata type can have multiple
|
||||
* unrelated answers in a response: ANY, RRSIG, or SIG.
|
||||
*/
|
||||
static inline bool
|
||||
dns_rdatatype_ismulti(dns_rdatatype_t type) {
|
||||
return type == dns_rdatatype_any || type == dns_rdatatype_rrsig ||
|
||||
type == dns_rdatatype_sig;
|
||||
}
|
||||
|
||||
/*%
|
||||
* Return true iff the rdata type is a signature: either RRSIG or SIG.
|
||||
*/
|
||||
static inline bool
|
||||
dns_rdatatype_issig(dns_rdatatype_t type) {
|
||||
return type == dns_rdatatype_rrsig || type == dns_rdatatype_sig;
|
||||
}
|
||||
|
||||
/*%
|
||||
* Return true iff the rdata type is an address: either A or AAAA.
|
||||
*/
|
||||
static inline bool
|
||||
dns_rdatatype_isaddr(dns_rdatatype_t type) {
|
||||
return type == dns_rdatatype_a || type == dns_rdatatype_aaaa;
|
||||
}
|
||||
|
||||
/*%
|
||||
* Return true iff the rdata type is an alias: either CNAME or DNAME.
|
||||
*/
|
||||
static inline bool
|
||||
dns_rdatatype_isalias(dns_rdatatype_t type) {
|
||||
return type == dns_rdatatype_cname || type == dns_rdatatype_dname;
|
||||
}
|
||||
|
||||
/*%
|
||||
* Return true iff the rdata class 'rdclass' is a meta-class
|
||||
* like ANY or NONE.
|
||||
*/
|
||||
static inline bool
|
||||
dns_rdataclass_ismeta(dns_rdataclass_t rdclass) {
|
||||
return rdclass == dns_rdataclass_reserved0 ||
|
||||
rdclass == dns_rdataclass_none || rdclass == dns_rdataclass_any;
|
||||
}
|
||||
|
||||
isc_result_t
|
||||
dns_rdata_additionaldata(dns_rdata_t *rdata, const dns_name_t *owner,
|
||||
@@ -653,101 +821,6 @@ dns_rdata_digest(dns_rdata_t *rdata, dns_digestfunc_t digest, void *arg);
|
||||
*\li Many other results are possible if not successful.
|
||||
*/
|
||||
|
||||
bool
|
||||
dns_rdatatype_questiononly(dns_rdatatype_t type);
|
||||
/*%<
|
||||
* Return true iff rdata of type 'type' can only appear in the question
|
||||
* section of a properly formatted message.
|
||||
*
|
||||
* Requires:
|
||||
* \li 'type' is a valid rdata type.
|
||||
*
|
||||
*/
|
||||
|
||||
bool
|
||||
dns_rdatatype_notquestion(dns_rdatatype_t type);
|
||||
/*%<
|
||||
* Return true iff rdata of type 'type' can not appear in the question
|
||||
* section of a properly formatted message.
|
||||
*
|
||||
* Requires:
|
||||
* \li 'type' is a valid rdata type.
|
||||
*
|
||||
*/
|
||||
|
||||
bool
|
||||
dns_rdatatype_atparent(dns_rdatatype_t type);
|
||||
/*%<
|
||||
* Return true iff rdata of type 'type' should appear at the parent of
|
||||
* a zone cut.
|
||||
*
|
||||
* Requires:
|
||||
* \li 'type' is a valid rdata type.
|
||||
*
|
||||
*/
|
||||
|
||||
bool
|
||||
dns_rdatatype_atcname(dns_rdatatype_t type);
|
||||
/*%<
|
||||
* Return true iff rdata of type 'type' can appear beside a cname.
|
||||
*
|
||||
* Requires:
|
||||
* \li 'type' is a valid rdata type.
|
||||
*
|
||||
*/
|
||||
|
||||
bool
|
||||
dns_rdatatype_followadditional(dns_rdatatype_t type);
|
||||
/*%<
|
||||
* Return true if adding a record of type 'type' to the ADDITIONAL section
|
||||
* of a message can itself trigger the addition of still more data to the
|
||||
* additional section.
|
||||
*
|
||||
* (For example: adding SRV to the ADDITIONAL section may trigger
|
||||
* the addition of address records associated with that SRV.)
|
||||
*
|
||||
* Requires:
|
||||
* \li 'type' is a valid rdata type.
|
||||
*
|
||||
*/
|
||||
|
||||
unsigned int
|
||||
dns_rdatatype_attributes(dns_rdatatype_t rdtype);
|
||||
/*%<
|
||||
* Return attributes for the given type.
|
||||
*
|
||||
* Requires:
|
||||
*\li 'rdtype' are known.
|
||||
*
|
||||
* Returns:
|
||||
*\li a bitmask consisting of the following flags.
|
||||
*/
|
||||
|
||||
/*% only one may exist for a name */
|
||||
#define DNS_RDATATYPEATTR_SINGLETON 0x00000001U
|
||||
/*% requires no other data be present */
|
||||
#define DNS_RDATATYPEATTR_EXCLUSIVE 0x00000002U
|
||||
/*% Is a meta type */
|
||||
#define DNS_RDATATYPEATTR_META 0x00000004U
|
||||
/*% Is a DNSSEC type, like RRSIG or NSEC */
|
||||
#define DNS_RDATATYPEATTR_DNSSEC 0x00000008U
|
||||
/*% Is a zone cut authority type */
|
||||
#define DNS_RDATATYPEATTR_ZONECUTAUTH 0x00000010U
|
||||
/*% Is reserved (unusable) */
|
||||
#define DNS_RDATATYPEATTR_RESERVED 0x00000020U
|
||||
/*% Is an unknown type */
|
||||
#define DNS_RDATATYPEATTR_UNKNOWN 0x00000040U
|
||||
/*% Is META, and can only be in a question section */
|
||||
#define DNS_RDATATYPEATTR_QUESTIONONLY 0x00000080U
|
||||
/*% Is META, and can NOT be in a question section */
|
||||
#define DNS_RDATATYPEATTR_NOTQUESTION 0x00000100U
|
||||
/*% Is present at zone cuts in the parent, not the child */
|
||||
#define DNS_RDATATYPEATTR_ATPARENT 0x00000200U
|
||||
/*% Can exist along side a CNAME */
|
||||
#define DNS_RDATATYPEATTR_ATCNAME 0x00000400U
|
||||
/*% Follow additional */
|
||||
#define DNS_RDATATYPEATTR_FOLLOWADDITIONAL 0x00000800U
|
||||
|
||||
dns_rdatatype_t
|
||||
dns_rdata_covers(dns_rdata_t *rdata);
|
||||
/*%<
|
||||
|
||||
@@ -105,30 +105,32 @@ typedef enum { dns_quotatype_zone = 0, dns_quotatype_server } dns_quotatype_t;
|
||||
* Options that modify how a 'fetch' is done.
|
||||
*/
|
||||
enum {
|
||||
DNS_FETCHOPT_TCP = 1 << 0, /*%< Use TCP. */
|
||||
DNS_FETCHOPT_UNSHARED = 1 << 1, /*%< See below. */
|
||||
DNS_FETCHOPT_RECURSIVE = 1 << 2, /*%< Set RD? */
|
||||
DNS_FETCHOPT_NOEDNS0 = 1 << 3, /*%< Do not use EDNS. */
|
||||
DNS_FETCHOPT_FORWARDONLY = 1 << 4, /*%< Only use forwarders. */
|
||||
DNS_FETCHOPT_NOVALIDATE = 1 << 5, /*%< Disable validation. */
|
||||
DNS_FETCHOPT_WANTNSID = 1 << 6, /*%< Request NSID */
|
||||
DNS_FETCHOPT_PREFETCH = 1 << 7, /*%< Do prefetch */
|
||||
DNS_FETCHOPT_NOCDFLAG = 1 << 8, /*%< Don't set CD flag. */
|
||||
DNS_FETCHOPT_NONTA = 1 << 9, /*%< Ignore NTA table. */
|
||||
DNS_FETCHOPT_NOCACHED = 1 << 10, /*%< Force cache update. */
|
||||
DNS_FETCHOPT_QMINIMIZE = 1 << 11, /*%< Use qname minimization. */
|
||||
DNS_FETCHOPT_NOFOLLOW = 1 << 12, /*%< Don't retrieve the NS RRset
|
||||
* from the child zone when a
|
||||
* delegation is returned in
|
||||
* response to a NS query. */
|
||||
DNS_FETCHOPT_QMIN_STRICT = 1 << 13, /*%< Do not work around servers
|
||||
* that return errors on
|
||||
* non-empty terminals. */
|
||||
DNS_FETCHOPT_QMIN_SKIP_IP6A = 1 << 14, /*%< Skip some labels when
|
||||
* doing qname minimization
|
||||
* on ip6.arpa. */
|
||||
DNS_FETCHOPT_NOFORWARD = 1 << 15, /*%< Do not use forwarders if
|
||||
* possible. */
|
||||
DNS_FETCHOPT_TCP = 1 << 0, /*%< Use TCP. */
|
||||
DNS_FETCHOPT_UNSHARED = 1 << 1, /*%< See below. */
|
||||
DNS_FETCHOPT_RECURSIVE = 1 << 2, /*%< Set RD? */
|
||||
DNS_FETCHOPT_NOEDNS0 = 1 << 3, /*%< Do not use EDNS. */
|
||||
DNS_FETCHOPT_FORWARDONLY = 1 << 4, /*%< Only use forwarders. */
|
||||
DNS_FETCHOPT_NOVALIDATE = 1 << 5, /*%< Disable validation. */
|
||||
DNS_FETCHOPT_WANTNSID = 1 << 6, /*%< Request NSID */
|
||||
DNS_FETCHOPT_PREFETCH = 1 << 7, /*%< Do prefetch */
|
||||
DNS_FETCHOPT_NOCDFLAG = 1 << 8, /*%< Don't set CD flag. */
|
||||
DNS_FETCHOPT_NONTA = 1 << 9, /*%< Ignore NTA table. */
|
||||
DNS_FETCHOPT_NOCACHED = 1 << 10, /*%< Force cache update. */
|
||||
DNS_FETCHOPT_QMINIMIZE = 1 << 11, /*%< Use qname minimization. */
|
||||
DNS_FETCHOPT_NOFOLLOW = 1 << 12, /*%< Don't retrieve the NS RRset
|
||||
* from the child zone when a
|
||||
* delegation is returned in
|
||||
* response to a NS query. */
|
||||
DNS_FETCHOPT_QMIN_STRICT = 1 << 13, /*%< Do not work around servers
|
||||
* that return errors on
|
||||
* non-empty terminals. */
|
||||
DNS_FETCHOPT_QMIN_SKIP_IP6A = 1 << 14, /*%< Skip some labels when
|
||||
* doing qname minimization
|
||||
* on ip6.arpa. */
|
||||
DNS_FETCHOPT_NOFORWARD = 1 << 15, /*%< Do not use forwarders if
|
||||
* possible. */
|
||||
DNS_FETCHOPT_QMINFETCH = 1 << 16, /*%< Qmin fetch */
|
||||
DNS_FETCHOPT_WANTZONEVERSION = 1 << 17, /*%< Request ZONEVERSION */
|
||||
|
||||
/*% EDNS version bits: */
|
||||
DNS_FETCHOPT_EDNSVERSIONSET = 1 << 23,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user