Browse Source

[2433] added a zone check function, just checking the number of NS and SOA

JINMEI Tatuya 12 years ago
parent
commit
0fd48fc7f5

+ 2 - 0
src/lib/dns/Makefile.am

@@ -95,6 +95,7 @@ lib_LTLIBRARIES = libb10-dns++.la
 libb10_dns___la_LDFLAGS = -no-undefined -version-info 2:0:0
 
 libb10_dns___la_SOURCES =
+libb10_dns___la_SOURCES += dns_fwd.h
 libb10_dns___la_SOURCES += edns.h edns.cc
 libb10_dns___la_SOURCES += exceptions.h exceptions.cc
 libb10_dns___la_SOURCES += master_lexer_inputsource.h master_lexer_inputsource.cc
@@ -129,6 +130,7 @@ libb10_dns___la_SOURCES += master_loader_callbacks.h master_loader_callbacks.cc
 libb10_dns___la_SOURCES += master_loader.h
 libb10_dns___la_SOURCES += rrset_collection_base.h
 libb10_dns___la_SOURCES += rrset_collection.h rrset_collection.cc
+libb10_dns___la_SOURCES += zone_checker.h zone_checker.cc
 libb10_dns___la_SOURCES += rdata/generic/detail/char_string.h
 libb10_dns___la_SOURCES += rdata/generic/detail/char_string.cc
 libb10_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h

+ 1 - 0
src/lib/dns/tests/Makefile.am

@@ -76,6 +76,7 @@ run_unittests_SOURCES += tsigrecord_unittest.cc
 run_unittests_SOURCES += character_string_unittest.cc
 run_unittests_SOURCES += master_loader_callbacks_test.cc
 run_unittests_SOURCES += rrset_collection_unittest.cc
+run_unittests_SOURCES += zone_checker_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 # We shouldn't need to include BOTAN_LIBS here, but there

+ 159 - 0
src/lib/dns/tests/zone_checker_unittest.cc

@@ -0,0 +1,159 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/zone_checker.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+#include <dns/rrset_collection.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/bind.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+using isc::Unexpected;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace {
+
+class ZoneCheckerTest : public ::testing::Test {
+protected:
+    ZoneCheckerTest() :
+        zname_("example.com"), zclass_(RRClass::IN()),
+        callbacks_(boost::bind(&ZoneCheckerTest::callback, this, _1, true),
+                   boost::bind(&ZoneCheckerTest::callback, this, _1, false))
+    {
+        soa_ = RRsetPtr(new RRset(zname_, zclass_, RRType::SOA(), RRTTL(60)));
+        soa_->addRdata(generic::SOA(
+                           "ns.example.com. root.example.com. 0 0 0 0 0"));
+        ns_ = RRsetPtr(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+        ns_->addRdata(generic::NS("ns.example.com"));
+        ns_->addRdata(generic::NS("ns2.example.com"));
+        rrsets_.reset(new RRsetCollection);
+    }
+
+    void callback(const std::string& reason, bool is_error) {
+        if (is_error) {
+            errors_.push_back(reason);
+        } else {
+            warns_.push_back(reason);
+        }
+    }
+
+    // Check stored issue messages with expected ones.  Clear vectors so
+    // the caller can check other cases.
+    void checkIssues() {
+        EXPECT_EQ(expected_errors_.size(), errors_.size());
+        for (int i = 0; i < std::min(expected_errors_.size(), errors_.size());
+             ++i) {
+            EXPECT_EQ(expected_errors_[i], errors_[i]);
+        }
+        EXPECT_EQ(expected_warns_.size(), warns_.size());
+        for (int i = 0; i < std::min(expected_warns_.size(), warns_.size());
+             ++i) {
+            EXPECT_EQ(expected_warns_[i], warns_[i]);
+        }
+
+        errors_.clear();
+        expected_errors_.clear();
+        warns_.clear();
+        expected_warns_.clear();
+    }
+
+    const Name zname_;
+    const RRClass zclass_;
+    RRsetPtr soa_;
+    RRsetPtr ns_;
+    boost::scoped_ptr<RRsetCollection> rrsets_;
+    std::vector<std::string> errors_;
+    std::vector<std::string> warns_;
+    std::vector<std::string> expected_errors_;
+    std::vector<std::string> expected_warns_;
+    ZoneCheckerCallbacks callbacks_;
+};
+
+TEST_F(ZoneCheckerTest, checkGood) {
+    // Checking a valid case.  No errors or warnings should be reported.
+    rrsets_->addRRset(soa_);
+    rrsets_->addRRset(ns_);
+    EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    checkIssues();
+}
+
+TEST_F(ZoneCheckerTest, checkSOA) {
+    // If the zone has no SOA it triggers an error.
+    rrsets_->addRRset(ns_);
+    EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    expected_errors_.push_back("zone example.com/IN has 0 SOA records");
+    checkIssues();
+
+    // If there are more than 1 SOA RR, it's also an error.
+    errors_.clear();
+    soa_->addRdata(generic::SOA("ns2.example.com. . 0 0 0 0 0"));
+    rrsets_->addRRset(soa_);
+    EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    expected_errors_.push_back("zone example.com/IN has 2 SOA records");
+    checkIssues();
+
+    // If the SOA RRset is "empty", it's treated as an implementation
+    // (rather than operational) error and results in an exception.
+    rrsets_->removeRRset(zname_, zclass_, RRType::SOA());
+    soa_.reset(new RRset(zname_, zclass_, RRType::SOA(), RRTTL(60)));
+    rrsets_->addRRset(soa_);
+    EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+    checkIssues();              // no error/warning should be reported
+
+    // Likewise, if the SOA RRset contains non SOA Rdata, it should be a bug.
+    rrsets_->removeRRset(zname_, zclass_, RRType::SOA());
+    soa_.reset(new RRset(zname_, zclass_, RRType::SOA(), RRTTL(60)));
+    soa_->addRdata(createRdata(RRType::NS(), zclass_, "ns.example.com"));
+    rrsets_->addRRset(soa_);
+    EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+    checkIssues();              // no error/warning should be reported
+}
+
+TEST_F(ZoneCheckerTest, checkNS) {
+    // If the zone has no NS at origin it triggers an error.
+    rrsets_->addRRset(soa_);
+    EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+    expected_errors_.push_back("zone example.com/IN has no NS records");
+    checkIssues();
+
+    // Check two buggy cases like the SOA tests
+    ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+    rrsets_->addRRset(ns_);
+    EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+    checkIssues();              // no error/warning should be reported
+
+    rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+    ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+    ns_->addRdata(createRdata(RRType::TXT(), zclass_, "ns.example.com"));
+    rrsets_->addRRset(ns_);
+    EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+    checkIssues();              // no error/warning should be reported
+}
+
+}

+ 129 - 0
src/lib/dns/zone_checker.cc

@@ -0,0 +1,129 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/zone_checker.h>
+
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
+#include <dns/rrset_collection_base.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+
+using boost::lexical_cast;
+using std::string;
+
+namespace isc {
+namespace dns {
+
+namespace {
+// This helper class is a trivial wrapper of ZoneCheckerCallbacks, and
+// remembers it if an error happens at least once.
+class CallbackWrapper {
+public:
+    CallbackWrapper(const ZoneCheckerCallbacks& callbacks) :
+        callbacks_(callbacks), has_error_(false)
+    {}
+
+    void error(const string& reason) {
+        has_error_ = true;
+        callbacks_.error(reason);
+    }
+
+    void warn(const string& reason) {
+        callbacks_.warn(reason);
+    }
+
+    bool hasError() const { return (has_error_); }
+
+private:
+    ZoneCheckerCallbacks callbacks_;
+    bool has_error_;
+};
+
+std::string
+zoneText(const Name& zone_name, const RRClass& zone_class) {
+    return (zone_name.toText(true) + "/" + zone_class.toText());
+}
+
+void
+checkSOA(const Name& zone_name, const RRClass& zone_class,
+         const RRsetCollectionBase& zone_rrsets, CallbackWrapper& callback) {
+    const AbstractRRset* rrset =
+        zone_rrsets.find(zone_name, RRType::SOA(), zone_class);
+    size_t count = 0;
+    if (rrset != NULL) {
+        for (RdataIteratorPtr rit = rrset->getRdataIterator();
+             !rit->isLast();
+             rit->next(), ++count) {
+            if (dynamic_cast<const rdata::generic::SOA*>(&rit->getCurrent()) ==
+                NULL) {
+                isc_throw(Unexpected, "Zone checker found bad RDATA in SOA");
+            }
+        }
+        if (count == 0) {
+            // this should be an implementation bug, not an operational error.
+            isc_throw(Unexpected, "Zone checker found an empty SOA RRset");
+        }
+    }
+    if (count != 1) {
+        callback.error("zone " + zoneText(zone_name, zone_class) + " has " +
+                       lexical_cast<string>(count) + " SOA records");
+    }
+}
+
+void
+checkNS(const Name& zone_name, const RRClass& zone_class,
+         const RRsetCollectionBase& zone_rrsets, CallbackWrapper& callback) {
+    const AbstractRRset* rrset =
+        zone_rrsets.find(zone_name, RRType::NS(), zone_class);
+    if (rrset == NULL) {
+        callback.error("zone " + zoneText(zone_name, zone_class) +
+                       " has no NS records");
+        return;
+    }
+    if (rrset->getRdataCount() == 0) {
+        // this should be an implementation bug, not an operational error.
+        isc_throw(Unexpected, "Zone checker found an empty NS RRset");
+    }
+
+    for (RdataIteratorPtr rit = rrset->getRdataIterator();
+         !rit->isLast();
+         rit->next()) {
+        if (dynamic_cast<const rdata::generic::NS*>(&rit->getCurrent()) ==
+            NULL) {
+            isc_throw(Unexpected, "Zone checker found bad RDATA in NS");
+        }
+    }
+}
+}
+
+bool
+checkZone(const Name& zone_name, const RRClass& zone_class,
+          const RRsetCollectionBase& zone_rrsets,
+          const ZoneCheckerCallbacks& callbacks) {
+    CallbackWrapper my_callback(callbacks);
+
+    checkSOA(zone_name, zone_class, zone_rrsets, my_callback);
+    checkNS(zone_name, zone_class, zone_rrsets, my_callback);
+
+    return (!my_callback.hasError());
+}
+
+} // end namespace dns
+} // end namespace isc

+ 61 - 0
src/lib/dns/zone_checker.h

@@ -0,0 +1,61 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef ZONE_CHECKER_H
+#define ZONE_CHECKER_H 1
+
+#include <dns/dns_fwd.h>
+
+#include <boost/function.hpp>
+
+#include <string>
+
+namespace isc {
+namespace dns {
+
+class ZoneCheckerCallbacks {
+public:
+    typedef boost::function<void(const std::string& reason)> IssueCallback;
+
+    ZoneCheckerCallbacks() :
+        error_callback_(nullCallback), warn_callback_(nullCallback)
+    {}
+
+    ZoneCheckerCallbacks(const IssueCallback& error_callback,
+                         const IssueCallback& warn_callback) :
+        error_callback_(error_callback), warn_callback_(warn_callback)
+    {}
+
+    void error(const std::string& reason) { error_callback_(reason); }
+    void warn(const std::string& reason) { warn_callback_(reason); }
+
+private:
+    static void nullCallback(const std::string&) {}
+
+    IssueCallback error_callback_;
+    IssueCallback warn_callback_;
+};
+
+bool
+checkZone(const Name& zone_name, const RRClass& zone_class,
+          const RRsetCollectionBase& zone_rrsets,
+          const ZoneCheckerCallbacks& callbacks = ZoneCheckerCallbacks());
+
+} // namespace dns
+} // namespace isc
+#endif  // ZONE_CHECKER_H
+
+// Local Variables:
+// mode: c++
+// End: