diff --git a/bin/tests/system/ede24/ns1/foo.fr.db b/bin/tests/system/ede24/ns1/foo.fr.db new file mode 100644 index 0000000000..f3937c043c --- /dev/null +++ b/bin/tests/system/ede24/ns1/foo.fr.db @@ -0,0 +1,21 @@ +; 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. + +$TTL 1 +foo.fr. IN SOA ns.foo.fr. op.foo.fr. ( + 3 ; serial + 1 ; refresh + 1 ; retry + 1 ; expire + 60 ; minimum + ) +foo.fr. NS ns.foo.fr. +ns.foo.fr. A 10.53.0.1 diff --git a/bin/tests/system/ede24/ns1/named.conf.j2 b/bin/tests/system/ede24/ns1/named.conf.j2 new file mode 100644 index 0000000000..d32cf6665d --- /dev/null +++ b/bin/tests/system/ede24/ns1/named.conf.j2 @@ -0,0 +1,36 @@ +/* + * 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 { + listen-on port @PORT@ { 10.53.0.1; }; + transfer-source 10.53.0.1; + pid-file "named.pid"; + recursion no; + also-notify { 10.53.0.2 port @PORT@; }; + notify-source 10.53.0.1; +}; + +zone "foo.fr" { + type primary; + allow-transfer{ 10.53.0.2; }; + file "foo.fr.db"; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff --git a/bin/tests/system/ede24/ns2/named.conf.j2 b/bin/tests/system/ede24/ns2/named.conf.j2 new file mode 100644 index 0000000000..18dc1236bb --- /dev/null +++ b/bin/tests/system/ede24/ns2/named.conf.j2 @@ -0,0 +1,35 @@ +/* + * 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 { + listen-on port @PORT@ { 10.53.0.2; }; + transfer-source 10.53.0.2; + pid-file "named.pid"; + recursion no; +}; + +zone "foo.fr" { + min-refresh-time 1; + min-retry-time 1; + type secondary; + primaries { 10.53.0.1 port @PORT@; }; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; diff --git a/bin/tests/system/ede24/tests_ede24.py b/bin/tests/system/ede24/tests_ede24.py new file mode 100644 index 0000000000..7c5771715e --- /dev/null +++ b/bin/tests/system/ede24/tests_ede24.py @@ -0,0 +1,71 @@ +# 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 isctest + + +def check_soa_noerror(): + msg = isctest.query.create("foo.fr", "SOA") + res = isctest.query.udp(msg, "10.53.0.2") + isctest.check.noerror(res) + + +def check_soa_servfail_ede24(edemsg): + msg = isctest.query.create("foo.fr", "SOA") + res = isctest.query.udp(msg, "10.53.0.2") + isctest.check.servfail(res) + + # Few CI machines uses old version of dnspython which doesn't supports + # EDNS, so we effectively bypass the check for those one. (It's fine, a + # bunch of other CI machines _does_ have recent version of dnspython). + if hasattr(res, "extended_errors"): + assert len(res.extended_errors()) == 1 + assert res.extended_errors()[0].to_text() == f"EDE 24 (Invalid Data): {edemsg}" + + +def test_ede24_noloaded(ns1, ns2): + # Sanity check that everything works first + check_soa_noerror() + + # Stop all servers, and we'll restart only ns2. + ns1.stop() + ns2.stop() + with ns2.watch_log_from_here() as watcher: + ns2.start(["--noclean", "--restart", "--port", os.environ["PORT"]]) + watcher.wait_for_line("failure trying primary 10.53.0.1") + + # ns2 attempts an XFR but ns1 since is off the zone DB can't be loaded. + check_soa_servfail_ede24("zone not loaded") + + +def test_ede24_expired(ns1, ns2): + # Restart ns1 then checks the server notify the zone in ns2 and ns2 serves + # the zone again. + with ns2.watch_log_from_here() as watcher: + ns1.start(["--noclean", "--restart", "--port", os.environ["PORT"]]) + watcher.wait_for_line("Transfer status: success") + check_soa_noerror() + + # Stop the primary and wait for expiration of the zone in the secondary. + with ns2.watch_log_from_here() as watcher: + ns1.stop() + watcher.wait_for_line(" zone foo.fr/IN: expired") + + # ns2 can't answer anymore. + check_soa_servfail_ede24("zone expired") + + # Restart the primary and wait for the zone to be back up again. + with ns2.watch_log_from_here() as watcher: + ns1.start(["--noclean", "--restart", "--port", os.environ["PORT"]]) + watcher.wait_for_line("Transfer status: success") + check_soa_noerror() diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index b45c842c9a..70b79471a4 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -2792,6 +2792,15 @@ dns_zone_getcfg(dns_zone_t *zone); * \li 'zone' to be a valid zone. */ +bool +dns_zone_isexpired(dns_zone_t *zone); +/*%< + * Return true if a (secondary, mirror, etc.) zone is expired + * + * Requires: + * \li 'zone\ to be a valid zone. + */ + #if DNS_ZONE_TRACE #define dns_zone_ref(ptr) dns_zone__ref(ptr, __func__, __FILE__, __LINE__) #define dns_zone_unref(ptr) dns_zone__unref(ptr, __func__, __FILE__, __LINE__) diff --git a/lib/dns/zone.c b/lib/dns/zone.c index d0bc8b65c4..fdba710f47 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -11848,13 +11848,13 @@ zone_expire(dns_zone_t *zone) { REQUIRE(LOCKED_ZONE(zone)); - dns_zone_log(zone, ISC_LOG_WARNING, "expired"); - DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_EXPIRED); zone->refresh = DNS_ZONE_DEFAULTREFRESH; zone->retry = DNS_ZONE_DEFAULTRETRY; DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_HAVETIMERS); + dns_zone_log(zone, ISC_LOG_WARNING, "expired"); + /* * An RPZ zone has expired; before unloading it, we must * first remove it from the RPZ summary database. The @@ -17617,6 +17617,7 @@ again: isc_time_compare(&expiretime, &zone->expiretime) > 0) { zone->expiretime = expiretime; + DNS_ZONE_CLRFLAG(zone, DNS_ZONEFLG_EXPIRED); } /* @@ -24249,3 +24250,10 @@ dns_zone_getcfg(dns_zone_t *zone) { return zone->cfg; } + +bool +dns_zone_isexpired(dns_zone_t *zone) { + REQUIRE(DNS_ZONE_VALID(zone)); + + return DNS_ZONE_FLAG(zone, DNS_ZONEFLG_EXPIRED); +} diff --git a/lib/ns/query.c b/lib/ns/query.c index 9f59e9a577..2a8f587bbf 100644 --- a/lib/ns/query.c +++ b/lib/ns/query.c @@ -1250,6 +1250,11 @@ query_getzonedb(ns_client_t *client, const dns_name_t *name, partial = true; } if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) { + if (dns_zone_isexpired(zone)) { + result = DNS_R_EXPIRED; + goto fail; + } + result = dns_zone_getdb(zone, &db); } @@ -5630,8 +5635,19 @@ ns__query_start(query_ctx_t *qctx) { QUERY_ERROR(qctx, DNS_R_REFUSED); } } else { + const char *edemsg = NULL; + CCTRACE(ISC_LOG_ERROR, "ns__query_start: query_getdb " "failed"); + + if (result == DNS_R_NOTLOADED) { + edemsg = "zone not loaded"; + } else if (result == DNS_R_EXPIRED) { + edemsg = "zone expired"; + } + dns_ede_add(&qctx->client->edectx, DNS_EDE_INVALIDDATA, + edemsg); + QUERY_ERROR(qctx, result); } return ns_query_done(qctx);