zone_checker.cc 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. // Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <dns/zone_checker.h>
  15. #include <dns/name.h>
  16. #include <dns/rdataclass.h>
  17. #include <dns/rrclass.h>
  18. #include <dns/rrtype.h>
  19. #include <dns/rrset.h>
  20. #include <dns/rrset_collection_base.h>
  21. #include <boost/bind.hpp>
  22. #include <boost/lexical_cast.hpp>
  23. #include <string>
  24. using boost::lexical_cast;
  25. using std::string;
  26. namespace isc {
  27. namespace dns {
  28. namespace {
  29. std::string
  30. zoneText(const Name& zone_name, const RRClass& zone_class) {
  31. return (zone_name.toText(true) + "/" + zone_class.toText());
  32. }
  33. void
  34. checkSOA(const Name& zone_name, const RRClass& zone_class,
  35. const RRsetCollectionBase& zone_rrsets,
  36. ZoneCheckerCallbacks& callback) {
  37. ConstRRsetPtr rrset =
  38. zone_rrsets.find(zone_name, zone_class, RRType::SOA());
  39. size_t count = 0;
  40. if (rrset) {
  41. for (RdataIteratorPtr rit = rrset->getRdataIterator();
  42. !rit->isLast();
  43. rit->next(), ++count) {
  44. if (dynamic_cast<const rdata::generic::SOA*>(&rit->getCurrent()) ==
  45. NULL) {
  46. isc_throw(Unexpected, "Zone checker found bad RDATA in SOA");
  47. }
  48. }
  49. if (count == 0) {
  50. // this should be an implementation bug, not an operational error.
  51. isc_throw(Unexpected, "Zone checker found an empty SOA RRset");
  52. }
  53. }
  54. if (count != 1) {
  55. callback.error("zone " + zoneText(zone_name, zone_class) + ": has " +
  56. lexical_cast<string>(count) + " SOA records");
  57. }
  58. }
  59. // Check if a target name is beyond zone cut, either due to delegation or
  60. // DNAME. Note that DNAME works on the origin but not on the name itself,
  61. // while delegation works on the name itself (but the NS at the origin is not
  62. // delegation).
  63. ConstRRsetPtr
  64. findZoneCut(const Name& zone_name, const RRClass& zone_class,
  65. const RRsetCollectionBase& zone_rrsets, const Name& target_name) {
  66. const unsigned int origin_count = zone_name.getLabelCount();
  67. const unsigned int target_count = target_name.getLabelCount();
  68. assert(origin_count <= target_count);
  69. for (unsigned int l = origin_count; l <= target_count; ++l) {
  70. const Name& mid_name = (l == target_count) ? target_name :
  71. target_name.split(target_count - l);
  72. ConstRRsetPtr found;
  73. if (l != origin_count &&
  74. (found = zone_rrsets.find(mid_name, zone_class, RRType::NS())) !=
  75. NULL) {
  76. return (found);
  77. }
  78. if (l != target_count &&
  79. (found = zone_rrsets.find(mid_name, zone_class, RRType::DNAME()))
  80. != NULL) {
  81. return (found);
  82. }
  83. }
  84. return (ConstRRsetPtr());
  85. }
  86. // Check if each "in-zone" NS name has an address record, identifying some
  87. // error cases.
  88. void
  89. checkNSNames(const Name& zone_name, const RRClass& zone_class,
  90. const RRsetCollectionBase& zone_rrsets,
  91. ConstRRsetPtr ns_rrset, ZoneCheckerCallbacks& callbacks) {
  92. if (ns_rrset->getRdataCount() == 0) {
  93. // this should be an implementation bug, not an operational error.
  94. isc_throw(Unexpected, "Zone checker found an empty NS RRset");
  95. }
  96. for (RdataIteratorPtr rit = ns_rrset->getRdataIterator();
  97. !rit->isLast();
  98. rit->next()) {
  99. const rdata::generic::NS* ns_data =
  100. dynamic_cast<const rdata::generic::NS*>(&rit->getCurrent());
  101. if (ns_data == NULL) {
  102. isc_throw(Unexpected, "Zone checker found bad RDATA in NS");
  103. }
  104. const Name& ns_name = ns_data->getNSName();
  105. const NameComparisonResult::NameRelation reln =
  106. ns_name.compare(zone_name).getRelation();
  107. if (reln != NameComparisonResult::EQUAL &&
  108. reln != NameComparisonResult::SUBDOMAIN) {
  109. continue; // not in the zone. we can ignore it.
  110. }
  111. // Check if there's a zone cut between the origin and the NS name.
  112. ConstRRsetPtr cut_rrset = findZoneCut(zone_name, zone_class,
  113. zone_rrsets, ns_name);
  114. if (cut_rrset) {
  115. if (cut_rrset->getType() == RRType::NS()) {
  116. continue; // delegation; making the NS name "out of zone".
  117. } else if (cut_rrset->getType() == RRType::DNAME()) {
  118. callbacks.error("zone " + zoneText(zone_name, zone_class) +
  119. ": NS '" + ns_name.toText(true) + "' is " +
  120. "below a DNAME '" +
  121. cut_rrset->getName().toText(true) +
  122. "' (illegal per RFC6672)");
  123. continue;
  124. } else {
  125. assert(false);
  126. }
  127. }
  128. if (zone_rrsets.find(ns_name, zone_class, RRType::CNAME()) != NULL) {
  129. callbacks.error("zone " + zoneText(zone_name, zone_class) +
  130. ": NS '" + ns_name.toText(true) + "' is a CNAME " +
  131. "(illegal per RFC2181)");
  132. continue;
  133. }
  134. if (zone_rrsets.find(ns_name, zone_class, RRType::A()) == NULL &&
  135. zone_rrsets.find(ns_name, zone_class, RRType::AAAA()) == NULL) {
  136. callbacks.warn("zone " + zoneText(zone_name, zone_class) +
  137. ": NS has no address records (A or AAAA)");
  138. }
  139. }
  140. }
  141. void
  142. checkNS(const Name& zone_name, const RRClass& zone_class,
  143. const RRsetCollectionBase& zone_rrsets,
  144. ZoneCheckerCallbacks& callbacks) {
  145. ConstRRsetPtr rrset =
  146. zone_rrsets.find(zone_name, zone_class, RRType::NS());
  147. if (rrset == NULL) {
  148. callbacks.error("zone " + zoneText(zone_name, zone_class) +
  149. ": has no NS records");
  150. return;
  151. }
  152. checkNSNames(zone_name, zone_class, zone_rrsets, rrset, callbacks);
  153. }
  154. // The following is a simple wrapper of checker callback so checkZone()
  155. // can also remember any critical errors.
  156. void
  157. errorWrapper(const string& reason, const ZoneCheckerCallbacks* callbacks,
  158. bool* had_error) {
  159. *had_error = true;
  160. callbacks->error(reason);
  161. }
  162. }
  163. bool
  164. checkZone(const Name& zone_name, const RRClass& zone_class,
  165. const RRsetCollectionBase& zone_rrsets,
  166. const ZoneCheckerCallbacks& callbacks) {
  167. bool had_error = false;
  168. ZoneCheckerCallbacks my_callbacks(
  169. boost::bind(errorWrapper, _1, &callbacks, &had_error),
  170. boost::bind(&ZoneCheckerCallbacks::warn, &callbacks, _1));
  171. checkSOA(zone_name, zone_class, zone_rrsets, my_callbacks);
  172. checkNS(zone_name, zone_class, zone_rrsets, my_callbacks);
  173. return (!had_error);
  174. }
  175. } // end namespace dns
  176. } // end namespace isc