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
23using boost::lexical_cast;
24using std::string;
25
26namespace isc {
27namespace dns {
28
29namespace {
30std::string
31zoneText(const Name& zone_name, const RRClass& zone_class) {
32 return (zone_name.toText(true) + "/" + zone_class.toText());
33}
34
35void
36checkSOA(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).
67findZoneCut(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.
94void
95checkNSNames(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
150void
151checkNS(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.
166void
167errorWrapper(const string& reason, const ZoneCheckerCallbacks* callbacks,
168 bool* had_error) {
169 *had_error = true;
170 callbacks->error(reason);
171}
172}
173
174bool
175checkZone(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
NameRelation
The relation of two names under comparison.
Definition: name.h:142
The Name class encapsulates DNS names.
Definition: name.h:223
The RRClass class encapsulates DNS resource record classes.
Definition: rrclass.h:98
static const RRType & CNAME()
Definition: rrtype.h:665
static const RRType & NS()
Definition: rrtype.h:497
static const RRType & DNAME()
Definition: rrtype.h:689
static const RRType & AAAA()
Definition: rrtype.h:467
static const RRType & SOA()
Definition: rrtype.h:485
static const RRType & A()
Definition: rrtype.h:677
Generic class to represent a set of RRsets.
Set of callbacks used in zone checks.
Definition: zone_checker.h:22
void warn(const std::string &reason) const
Call the callback for a non critical issue.
Definition: zone_checker.h:66
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
boost::shared_ptr< RdataIterator > RdataIteratorPtr
A pointer-like type point to an RdataIterator object.
Definition: rrset.h:63
bool checkZone(const Name &zone_name, const RRClass &zone_class, const RRsetCollectionBase &zone_rrsets, const ZoneCheckerCallbacks &callbacks)
Perform basic integrity checks on zone RRsets.
boost::shared_ptr< const AbstractRRset > ConstRRsetPtr
A pointer-like type pointing to an (immutable) RRset object.
Definition: rrset.h:60
Defines the logger used by the top-level component of kea-dhcp-ddns.