Kea  1.5.0
zone_checker.cc
Go to the documentation of this file.
1 // Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
9 #include <dns/zone_checker.h>
10 
11 #include <dns/name.h>
12 #include <dns/rdataclass.h>
13 #include <dns/rrclass.h>
14 #include <dns/rrtype.h>
15 #include <dns/rrset.h>
17 
18 #include <boost/bind.hpp>
19 #include <boost/lexical_cast.hpp>
20 
21 #include <string>
22 
23 using boost::lexical_cast;
24 using std::string;
25 
26 namespace isc {
27 namespace dns {
28 
29 namespace {
30 std::string
31 zoneText(const Name& zone_name, const RRClass& zone_class) {
32  return (zone_name.toText(true) + "/" + zone_class.toText());
33 }
34 
35 void
36 checkSOA(const Name& zone_name, const RRClass& zone_class,
37  const RRsetCollectionBase& zone_rrsets,
38  ZoneCheckerCallbacks& callback) {
39  ConstRRsetPtr rrset =
40  zone_rrsets.find(zone_name, zone_class, RRType::SOA());
41  size_t count = 0;
42  if (rrset) {
43  for (RdataIteratorPtr rit = rrset->getRdataIterator();
44  !rit->isLast();
45  rit->next(), ++count) {
46  if (dynamic_cast<const rdata::generic::SOA*>(&rit->getCurrent()) ==
47  NULL) {
48  isc_throw(Unexpected, "Zone checker found bad RDATA in SOA");
49  }
50  }
51  if (count == 0) {
52  // this should be an implementation bug, not an operational error.
53  isc_throw(Unexpected, "Zone checker found an empty SOA RRset");
54  }
55  }
56  if (count != 1) {
57  callback.error("zone " + zoneText(zone_name, zone_class) + ": has " +
58  lexical_cast<string>(count) + " SOA records");
59  }
60 }
61 
62 // Check if a target name is beyond zone cut, either due to delegation or
63 // DNAME. Note that DNAME works on the origin but not on the name itself,
64 // while delegation works on the name itself (but the NS at the origin is not
65 // delegation).
67 findZoneCut(const Name& zone_name, const RRClass& zone_class,
68  const RRsetCollectionBase& zone_rrsets, const Name& target_name) {
69  const unsigned int origin_count = zone_name.getLabelCount();
70  const unsigned int target_count = target_name.getLabelCount();
71  assert(origin_count <= target_count);
72 
73  for (unsigned int l = origin_count; l <= target_count; ++l) {
74  const Name& mid_name = (l == target_count) ? target_name :
75  target_name.split(target_count - l);
76 
77  ConstRRsetPtr found;
78  if (l != origin_count &&
79  (found = zone_rrsets.find(mid_name, zone_class, RRType::NS())) !=
80  NULL) {
81  return (found);
82  }
83  if (l != target_count &&
84  (found = zone_rrsets.find(mid_name, zone_class, RRType::DNAME()))
85  != NULL) {
86  return (found);
87  }
88  }
89  return (ConstRRsetPtr());
90 }
91 
92 // Check if each "in-zone" NS name has an address record, identifying some
93 // error cases.
94 void
95 checkNSNames(const Name& zone_name, const RRClass& zone_class,
96  const RRsetCollectionBase& zone_rrsets,
97  ConstRRsetPtr ns_rrset, ZoneCheckerCallbacks& callbacks) {
98  if (ns_rrset->getRdataCount() == 0) {
99  // this should be an implementation bug, not an operational error.
100  isc_throw(Unexpected, "Zone checker found an empty NS RRset");
101  }
102 
103  for (RdataIteratorPtr rit = ns_rrset->getRdataIterator();
104  !rit->isLast();
105  rit->next()) {
106  const rdata::generic::NS* ns_data =
107  dynamic_cast<const rdata::generic::NS*>(&rit->getCurrent());
108  if (ns_data == NULL) {
109  isc_throw(Unexpected, "Zone checker found bad RDATA in NS");
110  }
111  const Name& ns_name = ns_data->getNSName();
113  ns_name.compare(zone_name).getRelation();
114  if (reln != NameComparisonResult::EQUAL &&
116  continue; // not in the zone. we can ignore it.
117  }
118 
119  // Check if there's a zone cut between the origin and the NS name.
120  ConstRRsetPtr cut_rrset = findZoneCut(zone_name, zone_class,
121  zone_rrsets, ns_name);
122  if (cut_rrset) {
123  if (cut_rrset->getType() == RRType::NS()) {
124  continue; // delegation; making the NS name "out of zone".
125  } else if (cut_rrset->getType() == RRType::DNAME()) {
126  callbacks.error("zone " + zoneText(zone_name, zone_class) +
127  ": NS '" + ns_name.toText(true) + "' is " +
128  "below a DNAME '" +
129  cut_rrset->getName().toText(true) +
130  "' (illegal per RFC6672)");
131  continue;
132  } else {
133  assert(false);
134  }
135  }
136  if (zone_rrsets.find(ns_name, zone_class, RRType::CNAME()) != NULL) {
137  callbacks.error("zone " + zoneText(zone_name, zone_class) +
138  ": NS '" + ns_name.toText(true) + "' is a CNAME " +
139  "(illegal per RFC2181)");
140  continue;
141  }
142  if (zone_rrsets.find(ns_name, zone_class, RRType::A()) == NULL &&
143  zone_rrsets.find(ns_name, zone_class, RRType::AAAA()) == NULL) {
144  callbacks.warn("zone " + zoneText(zone_name, zone_class) +
145  ": NS has no address records (A or AAAA)");
146  }
147  }
148 }
149 
150 void
151 checkNS(const Name& zone_name, const RRClass& zone_class,
152  const RRsetCollectionBase& zone_rrsets,
153  ZoneCheckerCallbacks& callbacks) {
154  ConstRRsetPtr rrset =
155  zone_rrsets.find(zone_name, zone_class, RRType::NS());
156  if (rrset == NULL) {
157  callbacks.error("zone " + zoneText(zone_name, zone_class) +
158  ": has no NS records");
159  return;
160  }
161  checkNSNames(zone_name, zone_class, zone_rrsets, rrset, callbacks);
162 }
163 
164 // The following is a simple wrapper of checker callback so checkZone()
165 // can also remember any critical errors.
166 void
167 errorWrapper(const string& reason, const ZoneCheckerCallbacks* callbacks,
168  bool* had_error) {
169  *had_error = true;
170  callbacks->error(reason);
171 }
172 }
173 
174 bool
175 checkZone(const Name& zone_name, const RRClass& zone_class,
176  const RRsetCollectionBase& zone_rrsets,
177  const ZoneCheckerCallbacks& callbacks) {
178  bool had_error = false;
179  ZoneCheckerCallbacks my_callbacks(
180  boost::bind(errorWrapper, _1, &callbacks, &had_error),
181  boost::bind(&ZoneCheckerCallbacks::warn, &callbacks, _1));
182 
183  checkSOA(zone_name, zone_class, zone_rrsets, my_callbacks);
184  checkNS(zone_name, zone_class, zone_rrsets, my_callbacks);
185 
186  return (!had_error);
187 }
188 
189 } // end namespace dns
190 } // end namespace isc
The Name class encapsulates DNS names.
Definition: name.h:223
static const RRType & CNAME()
Definition: rrtype.h:665
bool checkZone(const Name &zone_name, const RRClass &zone_class, const RRsetCollectionBase &zone_rrsets, const ZoneCheckerCallbacks &callbacks)
Perform basic integrity checks on zone RRsets.
static const RRType & SOA()
Definition: rrtype.h:485
void warn(const std::string &reason) const
Call the callback for a non critical issue.
Definition: zone_checker.h:66
Set of callbacks used in zone checks.
Definition: zone_checker.h:22
static const RRType & NS()
Definition: rrtype.h:497
The RRClass class encapsulates DNS resource record classes.
Definition: rrclass.h:98
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
static const RRType & A()
Definition: rrtype.h:677
Defines the logger used by the top-level component of kea-dhcp-ddns.
static const RRType & DNAME()
Definition: rrtype.h:689
boost::shared_ptr< RdataIterator > RdataIteratorPtr
A pointer-like type point to an RdataIterator object.
Definition: rrset.h:63
boost::shared_ptr< const AbstractRRset > ConstRRsetPtr
A pointer-like type pointing to an (immutable) RRset object.
Definition: rrset.h:60
NameRelation
The relation of two names under comparison.
Definition: name.h:142
static const RRType & AAAA()
Definition: rrtype.h:467
Generic class to represent a set of RRsets.