diff --git a/bin/check/check-tool.c b/bin/check/check-tool.c index 5ad43f2993..98924e3dfc 100644 --- a/bin/check/check-tool.c +++ b/bin/check/check-tool.c @@ -144,12 +144,97 @@ logged(char *key, int value) { return false; } +static bool +checkisservedby(dns_zone_t *zone, dns_rdatatype_t type, + const dns_name_t *name) { + char namebuf[DNS_NAME_FORMATSIZE + 1]; + char ownerbuf[DNS_NAME_FORMATSIZE + 1]; + /* + * Not all getaddrinfo implementations distinguish NODATA + * from NXDOMAIN with PF_INET6 so use PF_UNSPEC and look at + * the returned ai_family values. + */ + struct addrinfo hints = { + .ai_flags = AI_CANONNAME, + .ai_family = PF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + }; + struct addrinfo *ai = NULL, *cur; + bool has_type = false; + int eai; + + dns_name_format(name, namebuf, sizeof(namebuf) - 1); + /* + * Turn off search. + */ + if (dns_name_countlabels(name) > 1U) { + strlcat(namebuf, ".", sizeof(namebuf)); + } + eai = getaddrinfo(namebuf, NULL, &hints, &ai); + + switch (eai) { + case 0: + cur = ai; + while (cur != NULL) { + if (cur->ai_family == AF_INET && + type == dns_rdatatype_a) + { + has_type = true; + break; + } + if (cur->ai_family == AF_INET6 && + type == dns_rdatatype_aaaa) + { + has_type = true; + break; + } + cur = cur->ai_next; + } + freeaddrinfo(ai); + return has_type; +#if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME) + case EAI_NODATA: +#endif /* if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME) */ + case EAI_NONAME: + if (!logged(namebuf, ERR_NO_ADDRESSES)) { + dns_name_format(dns_zone_getorigin(zone), ownerbuf, + sizeof(ownerbuf)); + dns_name_format(name, namebuf, sizeof(namebuf) - 1); + dns_zone_log(zone, ISC_LOG_ERROR, + "%s/NS '%s' (out of zone) " + "has no addresses records (A or AAAA)", + ownerbuf, namebuf); + add(namebuf, ERR_NO_ADDRESSES); + } + return false; + default: + if (!logged(namebuf, ERR_LOOKUP_FAILURE)) { + dns_name_format(dns_zone_getorigin(zone), ownerbuf, + sizeof(ownerbuf)); + dns_name_format(name, namebuf, sizeof(namebuf) - 1); + dns_zone_log(zone, ISC_LOG_WARNING, + "getaddrinfo(%s) failed: %s", namebuf, + gai_strerror(eai)); + add(namebuf, ERR_LOOKUP_FAILURE); + } + return true; + } +} + static bool checkns(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner, dns_rdataset_t *a, dns_rdataset_t *aaaa) { dns_rdataset_t *rdataset; dns_rdata_t rdata = DNS_RDATA_INIT; - struct addrinfo hints, *ai, *cur; + isc_result_t result; + struct addrinfo hints = { + .ai_flags = AI_CANONNAME, + .ai_family = PF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + }; + struct addrinfo *ai = NULL, *cur; char namebuf[DNS_NAME_FORMATSIZE + 1]; char ownerbuf[DNS_NAME_FORMATSIZE]; char addrbuf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:123.123.123.123")]; @@ -157,7 +242,7 @@ checkns(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner, bool match; const char *type; void *ptr = NULL; - int result; + int eai; REQUIRE(a == NULL || !dns_rdataset_isassociated(a) || a->type == dns_rdatatype_a); @@ -168,12 +253,6 @@ checkns(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner, return answer; } - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = AI_CANONNAME; - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - dns_name_format(name, namebuf, sizeof(namebuf) - 1); /* * Turn off search. @@ -183,9 +262,9 @@ checkns(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner, } dns_name_format(owner, ownerbuf, sizeof(ownerbuf)); - result = getaddrinfo(namebuf, NULL, &hints, &ai); + eai = getaddrinfo(namebuf, NULL, &hints, &ai); dns_name_format(name, namebuf, sizeof(namebuf) - 1); - switch (result) { + switch (eai) { case 0: /* * Work around broken getaddrinfo() implementations that @@ -228,7 +307,7 @@ checkns(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner, if (!logged(namebuf, ERR_LOOKUP_FAILURE)) { dns_zone_log(zone, ISC_LOG_WARNING, "getaddrinfo(%s) failed: %s", namebuf, - gai_strerror(result)); + gai_strerror(eai)); add(namebuf, ERR_LOOKUP_FAILURE); } return true; @@ -358,25 +437,27 @@ checkmissing: add(namebuf, ERR_MISSING_GLUE); } } - freeaddrinfo(ai); + if (ai != NULL) { + freeaddrinfo(ai); + } return answer; } static bool checkmx(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner) { - struct addrinfo hints, *ai, *cur; + struct addrinfo hints = { + .ai_flags = AI_CANONNAME, + .ai_family = PF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + }; + struct addrinfo *ai = NULL, *cur; char namebuf[DNS_NAME_FORMATSIZE + 1]; char ownerbuf[DNS_NAME_FORMATSIZE]; - int result; + int eai; int level = ISC_LOG_ERROR; bool answer = true; - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = AI_CANONNAME; - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - dns_name_format(name, namebuf, sizeof(namebuf) - 1); /* * Turn off search. @@ -386,9 +467,9 @@ checkmx(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner) { } dns_name_format(owner, ownerbuf, sizeof(ownerbuf)); - result = getaddrinfo(namebuf, NULL, &hints, &ai); + eai = getaddrinfo(namebuf, NULL, &hints, &ai); dns_name_format(name, namebuf, sizeof(namebuf) - 1); - switch (result) { + switch (eai) { case 0: /* * Work around broken getaddrinfo() implementations that @@ -421,7 +502,9 @@ checkmx(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner) { } } } - freeaddrinfo(ai); + if (ai != NULL) { + freeaddrinfo(ai); + } return answer; case EAI_NONAME: @@ -442,7 +525,7 @@ checkmx(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner) { if (!logged(namebuf, ERR_LOOKUP_FAILURE)) { dns_zone_log(zone, ISC_LOG_WARNING, "getaddrinfo(%s) failed: %s", namebuf, - gai_strerror(result)); + gai_strerror(eai)); add(namebuf, ERR_LOOKUP_FAILURE); } return true; @@ -451,19 +534,19 @@ checkmx(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner) { static bool checksrv(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner) { - struct addrinfo hints, *ai, *cur; + struct addrinfo hints = { + .ai_flags = AI_CANONNAME, + .ai_family = PF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + }; + struct addrinfo *ai = NULL, *cur; char namebuf[DNS_NAME_FORMATSIZE + 1]; char ownerbuf[DNS_NAME_FORMATSIZE]; - int result; + int eai; int level = ISC_LOG_ERROR; bool answer = true; - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = AI_CANONNAME; - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - dns_name_format(name, namebuf, sizeof(namebuf) - 1); /* * Turn off search. @@ -473,9 +556,9 @@ checksrv(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner) { } dns_name_format(owner, ownerbuf, sizeof(ownerbuf)); - result = getaddrinfo(namebuf, NULL, &hints, &ai); + eai = getaddrinfo(namebuf, NULL, &hints, &ai); dns_name_format(name, namebuf, sizeof(namebuf) - 1); - switch (result) { + switch (eai) { case 0: /* * Work around broken getaddrinfo() implementations that @@ -508,7 +591,9 @@ checksrv(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner) { } } } - freeaddrinfo(ai); + if (ai != NULL) { + freeaddrinfo(ai); + } return answer; case EAI_NONAME: @@ -529,7 +614,7 @@ checksrv(dns_zone_t *zone, const dns_name_t *name, const dns_name_t *owner) { if (!logged(namebuf, ERR_LOOKUP_FAILURE)) { dns_zone_log(zone, ISC_LOG_WARNING, "getaddrinfo(%s) failed: %s", namebuf, - gai_strerror(result)); + gai_strerror(eai)); add(namebuf, ERR_LOOKUP_FAILURE); } return true; @@ -603,6 +688,7 @@ load_zone(isc_mem_t *mctx, const char *zonename, const char *filename, } if (docheckns) { dns_zone_setcheckns(zone, checkns); + dns_zone_setcheckisservedby(zone, checkisservedby); } if (dochecksrv) { dns_zone_setchecksrv(zone, checksrv); diff --git a/bin/check/named-checkzone.rst b/bin/check/named-checkzone.rst index 9b8a0909a4..a58cfb7a14 100644 --- a/bin/check/named-checkzone.rst +++ b/bin/check/named-checkzone.rst @@ -91,9 +91,13 @@ Options (both in-zone and out-of-zone hostnames). Mode ``local`` only checks SRV records which refer to in-zone hostnames. + Mode ``full`` checks that a zone that has A or AAAA records it is served + by a server with the same type of address records. + Mode ``full`` checks that delegation NS records refer to A or AAAA records (both in-zone and out-of-zone hostnames). It also checks that glue address records in the zone match those advertised by the child. + Mode ``local`` only checks NS records which refer to in-zone hostnames or verifies that some required glue exists, i.e., when the name server is in a child zone. diff --git a/bin/tests/system/checkzone/tests.sh b/bin/tests/system/checkzone/tests.sh index aa26c8fb43..fb6bcab82a 100644 --- a/bin/tests/system/checkzone/tests.sh +++ b/bin/tests/system/checkzone/tests.sh @@ -218,5 +218,41 @@ echo $lines if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) +echo_i "Checking for 'zone has A records but is not served by IPv4 servers' warning ($n)" +ret=0 +$CHECKZONE example zones/warn.no-a.server.db >test.out1.$n 2>&1 || ret=1 +grep "zone has A records but is not served by IPv4 servers" test.out1.$n >/dev/null || ret=1 +grep "zone has AAAA records but is not served by IPv6 servers" test.out1.$n >/dev/null && ret=1 +n=$((n + 1)) +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +echo_i "Checking for 'zone has AAAA records but is not served by IPv6 servers' warning ($n)" +ret=0 +$CHECKZONE example zones/warn.no-aaaa.server.db >test.out1.$n 2>&1 || ret=1 +grep "zone has AAAA records but is not served by IPv6 servers" test.out1.$n >/dev/null || ret=1 +grep "zone has A records but is not served by IPv4 servers" test.out1.$n >/dev/null && ret=1 +n=$((n + 1)) +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +echo_i "Checking for 'zone has A records but is not served by IPv4 servers' warning for glue ($n)" +ret=0 +$CHECKZONE example zones/warn.no-a.server.glue.db >test.out1.$n 2>&1 || ret=1 +grep "zone has A records but is not served by IPv4 servers" test.out1.$n >/dev/null || ret=1 +grep "zone has AAAA records but is not served by IPv6 servers" test.out1.$n >/dev/null && ret=1 +n=$((n + 1)) +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +echo_i "Checking for 'zone has AAAA records but is not served by IPv6 servers' warning for glue ($n)" +ret=0 +$CHECKZONE example zones/warn.no-aaaa.server.glue.db >test.out1.$n 2>&1 || ret=1 +grep "zone has AAAA records but is not served by IPv6 servers" test.out1.$n >/dev/null || ret=1 +grep "zone has A records but is not served by IPv4 servers" test.out1.$n >/dev/null && ret=1 +n=$((n + 1)) +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + echo_i "exit status: $status" [ $status -eq 0 ] || exit 1 diff --git a/bin/tests/system/checkzone/zones/warn.no-a.server.db b/bin/tests/system/checkzone/zones/warn.no-a.server.db new file mode 100644 index 0000000000..a27c247f02 --- /dev/null +++ b/bin/tests/system/checkzone/zones/warn.no-a.server.db @@ -0,0 +1,22 @@ +; 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 IN SOA ns1 hostmaster 2017052201 3600 600 604800 30 +@ 7200 IN NS ns1 +@ 7200 IN NS ns2 +@ 7200 IN NS ns3 +@ 7200 IN NS ns4 +ns1 3600 IN AAAA fd92:7065:b8e:ffff::1 +ns2 3600 IN AAAA fd92:7065:b8e:ffff::2 +ns3 3600 IN AAAA fd92:7065:b8e:ffff::4 +ns4 3600 IN AAAA fd92:7065:b8e:ffff::4 +dualstack 300 IN AAAA 2001:db8::1 +dualstack 300 IN A 10.53.0.5 diff --git a/bin/tests/system/checkzone/zones/warn.no-a.server.glue.db b/bin/tests/system/checkzone/zones/warn.no-a.server.glue.db new file mode 100644 index 0000000000..0bbfdde4e7 --- /dev/null +++ b/bin/tests/system/checkzone/zones/warn.no-a.server.glue.db @@ -0,0 +1,23 @@ +; 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 IN SOA ns1 hostmaster 2017052201 3600 600 604800 30 +@ 7200 IN NS ns1 +@ 7200 IN NS ns2 +@ 7200 IN NS ns3 +@ 7200 IN NS ns4 +ns1 3600 IN AAAA fd92:7065:b8e:ffff::1 +ns2 3600 IN AAAA fd92:7065:b8e:ffff::2 +ns3 3600 IN AAAA fd92:7065:b8e:ffff::4 +ns4 3600 IN AAAA fd92:7065:b8e:ffff::4 +child 3600 IN NS ns1.child +ns1.child 300 IN AAAA 2001:db8::1 +ns1.child 300 IN A 10.53.0.5 diff --git a/bin/tests/system/checkzone/zones/warn.no-aaaa.server.db b/bin/tests/system/checkzone/zones/warn.no-aaaa.server.db new file mode 100644 index 0000000000..afb52d3743 --- /dev/null +++ b/bin/tests/system/checkzone/zones/warn.no-aaaa.server.db @@ -0,0 +1,22 @@ +; 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 IN SOA ns1 hostmaster 2017052201 3600 600 604800 30 +@ 7200 IN NS ns1 +@ 7200 IN NS ns2 +@ 7200 IN NS ns3 +@ 7200 IN NS ns4 +ns1 3600 IN A 10.53.0.1 +ns2 3600 IN A 10.53.0.2 +ns3 3600 IN A 10.53.0.4 +ns4 3600 IN A 10.53.0.4 +dualstack 300 IN AAAA 2001:db8::1 +dualstack 300 IN A 10.53.0.5 diff --git a/bin/tests/system/checkzone/zones/warn.no-aaaa.server.glue.db b/bin/tests/system/checkzone/zones/warn.no-aaaa.server.glue.db new file mode 100644 index 0000000000..165a1141bb --- /dev/null +++ b/bin/tests/system/checkzone/zones/warn.no-aaaa.server.glue.db @@ -0,0 +1,23 @@ +; 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 IN SOA ns1 hostmaster 2017052201 3600 600 604800 30 +@ 7200 IN NS ns1 +@ 7200 IN NS ns2 +@ 7200 IN NS ns3 +@ 7200 IN NS ns4 +ns1 3600 IN A 10.53.0.1 +ns2 3600 IN A 10.53.0.2 +ns3 3600 IN A 10.53.0.4 +ns4 3600 IN A 10.53.0.4 +child 3600 IN NS ns1.child +ns1.child 300 IN AAAA 2001:db8::1 +ns1.child 300 IN A 10.53.0.5 diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h index 1e56d1d256..7ed00cb411 100644 --- a/lib/dns/include/dns/types.h +++ b/lib/dns/include/dns/types.h @@ -448,6 +448,9 @@ typedef bool (*dns_checknsfunc_t)(dns_zone_t *, const dns_name_t *, const dns_name_t *, dns_rdataset_t *, dns_rdataset_t *); +typedef bool (*dns_checkisservedbyfunc_t)(dns_zone_t *, dns_rdatatype_t type, + const dns_name_t *); + typedef bool (*dns_isselffunc_t)(dns_view_t *, dns_tsigkey_t *, const isc_sockaddr_t *, const isc_sockaddr_t *, dns_rdataclass_t, void *); diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index 8165c5a67f..23c89e5197 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -2187,6 +2187,18 @@ dns_zone_setcheckns(dns_zone_t *zone, dns_checknsfunc_t checkns); * 'zone' to be a valid zone. */ +void +dns_zone_setcheckisservedby(dns_zone_t *zone, + dns_checkisservedbyfunc_t checkisserverby); +/*%< + * Set the post load integrity callback function 'checkisserverby'. + * 'checkisserverby' will be called if the NS TARGET is not within + * the zone and there are A or AAAA records in the the zone. + * + * Require: + * 'zone' to be a valid zone. + */ + void dns_zone_setnotifydelay(dns_zone_t *zone, uint32_t delay); /*%< diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 3e08b77da1..00e2bdb55d 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -377,6 +377,7 @@ struct dns_zone { dns_checkmxfunc_t checkmx; dns_checksrvfunc_t checksrv; dns_checknsfunc_t checkns; + dns_checkisservedbyfunc_t checkisservedby; /*% * Zones in certain states such as "waiting for zone transfer" * or "zone transfer in progress" are kept on per-state linked lists @@ -2875,8 +2876,8 @@ zone_check_srv(dns_zone_t *zone, dns_db_t *db, dns_name_t *name, } static bool -zone_check_glue(dns_zone_t *zone, dns_db_t *db, dns_name_t *name, - dns_name_t *owner) { +zone_check_glue(dns_zone_t *zone, dns_db_t *db, bool *has_a, bool *has_aaaa, + dns_name_t *name, dns_name_t *owner) { bool answer = true; isc_result_t result, tresult; char ownerbuf[DNS_NAME_FORMATSIZE]; @@ -2928,8 +2929,22 @@ zone_check_glue(dns_zone_t *zone, dns_db_t *db, dns_name_t *name, NULL); } if (result == ISC_R_SUCCESS) { + SET_IF_NOT_NULL(has_a, true); dns_rdataset_disassociate(&a); + if (has_aaaa != NULL && !*has_aaaa) { + result = dns_db_find(db, name, NULL, dns_rdatatype_aaaa, + DNS_DBFIND_GLUEOK, 0, NULL, + foundname, &aaaa, NULL); + if (result == ISC_R_SUCCESS) { + *has_aaaa = true; + } + if (dns_rdataset_isassociated(&aaaa)) { + dns_rdataset_disassociate(&aaaa); + } + } return true; + } else if (result == DNS_R_GLUE && has_a != NULL) { + *has_a = true; } else if (result == DNS_R_DELEGATION) { dns_rdataset_disassociate(&a); } @@ -2944,12 +2959,16 @@ zone_check_glue(dns_zone_t *zone, dns_db_t *db, dns_name_t *name, if (dns_rdataset_isassociated(&a)) { dns_rdataset_disassociate(&a); } + SET_IF_NOT_NULL(has_aaaa, true); dns_rdataset_disassociate(&aaaa); return true; } if (tresult == DNS_R_DELEGATION || tresult == DNS_R_DNAME) { dns_rdataset_disassociate(&aaaa); } + if (tresult == DNS_R_GLUE && has_aaaa != NULL) { + *has_aaaa = true; + } if (result == DNS_R_GLUE || tresult == DNS_R_GLUE) { /* * Check glue against child zone. @@ -3173,6 +3192,46 @@ isspf(const dns_rdata_t *rdata) { return false; } +static bool +zone_is_served_by(dns_zone_t *zone, dns_db_t *db, dns_rdatatype_t type, + dns_name_t *name) { + dns_rdataset_t rdataset; + dns_fixedname_t found; + dns_name_t *foundname = dns_fixedname_initname(&found); + isc_result_t result; + + /* + * Outside of zone, assume good when loading in named. + */ + if (!dns_name_issubdomain(name, &zone->origin)) { + if (zone->checkisservedby != NULL) { + return zone->checkisservedby(zone, type, name); + } + return true; + } + + dns_rdataset_init(&rdataset); + result = dns_db_find(db, name, NULL, type, 0, 0, NULL, foundname, + &rdataset, NULL); + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + switch (result) { + case DNS_R_DELEGATION: + if (zone->checkisservedby != NULL) { + return zone->checkisservedby(zone, type, name); + } + /* + * Treat as success. + */ + return true; + case ISC_R_SUCCESS: + return true; + default: + return false; + } +} + static bool integrity_checks(dns_zone_t *zone, dns_db_t *db) { dns_dbiterator_t *dbiterator = NULL; @@ -3188,6 +3247,8 @@ integrity_checks(dns_zone_t *zone, dns_db_t *db) { dns_name_t *bottom; isc_result_t result; bool ok = true, have_spf, have_txt; + bool has_a = false; + bool has_aaaa = false; int level; char namebuf[DNS_NAME_FORMATSIZE]; @@ -3242,7 +3303,9 @@ integrity_checks(dns_zone_t *zone, dns_db_t *db) { dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &ns, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); - if (!zone_check_glue(zone, db, &ns.name, name)) { + if (!zone_check_glue(zone, db, &has_a, &has_aaaa, + &ns.name, name)) + { ok = false; } dns_rdata_reset(&rdata); @@ -3306,7 +3369,7 @@ integrity_checks(dns_zone_t *zone, dns_db_t *db) { result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_srv, 0, 0, &rdataset, NULL); if (result != ISC_R_SUCCESS) { - goto checkspf; + goto checkforaaaa; } result = dns_rdataset_first(&rdataset); while (result == ISC_R_SUCCESS) { @@ -3321,7 +3384,29 @@ integrity_checks(dns_zone_t *zone, dns_db_t *db) { } dns_rdataset_disassociate(&rdataset); - checkspf: + checkforaaaa: + /* + * Check if there is an A or AAAA RRset in the zone. + */ + if (!has_a) { + result = dns_db_findrdataset(db, node, NULL, + dns_rdatatype_a, 0, 0, + &rdataset, NULL); + if (result == ISC_R_SUCCESS) { + has_a = true; + dns_rdataset_disassociate(&rdataset); + } + } + if (!has_aaaa) { + result = dns_db_findrdataset(db, node, NULL, + dns_rdatatype_aaaa, 0, 0, + &rdataset, NULL); + if (result == ISC_R_SUCCESS) { + has_aaaa = true; + dns_rdataset_disassociate(&rdataset); + } + } + /* * Check if there is a type SPF record without an * SPF-formatted type TXT record also being present. @@ -3371,6 +3456,70 @@ integrity_checks(dns_zone_t *zone, dns_db_t *db) { result = dns_dbiterator_next(dbiterator); } + if (has_a) { + has_a = false; + result = dns_db_find(db, &zone->origin, NULL, dns_rdatatype_ns, + 0, 0, NULL, name, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + goto cleanup; + } + result = dns_rdataset_first(&rdataset); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &ns, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdata_reset(&rdata); + if (zone_is_served_by(zone, db, dns_rdatatype_a, + &ns.name)) + { + has_a = true; + break; + } + result = dns_rdataset_next(&rdataset); + } + dns_rdataset_disassociate(&rdataset); + if (!has_a) { + dns_zone_log(zone, ISC_LOG_WARNING, + "zone has A records but is not served " + "by IPv4 servers"); + } + } + + if (has_aaaa) { + has_aaaa = false; + result = dns_db_find(db, &zone->origin, NULL, dns_rdatatype_ns, + 0, 0, NULL, name, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + goto cleanup; + } + result = dns_rdataset_first(&rdataset); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &ns, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + dns_rdata_reset(&rdata); + if (zone_is_served_by(zone, db, dns_rdatatype_aaaa, + &ns.name)) + { + has_aaaa = true; + break; + } + result = dns_rdataset_next(&rdataset); + } + dns_rdataset_disassociate(&rdataset); + if (!has_aaaa) { + dns_zone_log(zone, ISC_LOG_WARNING, + "zone has AAAA records but is not served " + "by IPv6 servers"); + } + } + cleanup: if (node != NULL) { dns_db_detachnode(db, &node); @@ -20092,6 +20241,13 @@ dns_zone_setcheckns(dns_zone_t *zone, dns_checknsfunc_t checkns) { zone->checkns = checkns; } +void +dns_zone_setcheckisservedby(dns_zone_t *zone, + dns_checkisservedbyfunc_t checkisservedby) { + REQUIRE(DNS_ZONE_VALID(zone)); + zone->checkisservedby = checkisservedby; +} + void dns_zone_setisself(dns_zone_t *zone, dns_isselffunc_t isself, void *arg) { REQUIRE(DNS_ZONE_VALID(zone));