Commit b8ea222a authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

merged trac #423: simple master file loader.


git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@3857 e5f2f494-b856-4b98-b285-d166d9295462
parents c2d30a88 68d8e0fb
......@@ -69,6 +69,7 @@ libdns___la_SOURCES += dnssectime.h dnssectime.cc
libdns___la_SOURCES += edns.h edns.cc
libdns___la_SOURCES += exceptions.h exceptions.cc
libdns___la_SOURCES += util/hex.h
libdns___la_SOURCES += masterload.h masterload.cc
libdns___la_SOURCES += message.h message.cc
libdns___la_SOURCES += messagerenderer.h messagerenderer.cc
libdns___la_SOURCES += name.h name.cc
......
// Copyright (C) 2010 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 <istream>
#include <fstream>
#include <sstream>
#include <string>
#include <cctype>
#include <boost/scoped_ptr.hpp>
#include <exceptions/exceptions.h>
#include <dns/masterload.h>
#include <dns/name.h>
#include <dns/rdata.h>
#include <dns/rrclass.h>
#include <dns/rrset.h>
#include <dns/rrttl.h>
#include <dns/rrtype.h>
using namespace std;
using namespace boost;
using namespace isc::dns::rdata;
namespace isc {
namespace dns {
void
masterLoad(const char* const filename, const Name& origin,
const RRClass& zone_class, MasterLoadCallback callback)
{
ifstream ifs;
ifs.open(filename, ios_base::in);
if (ifs.fail()) {
isc_throw(MasterLoadError, "Failed to open master file: " << filename);
}
masterLoad(ifs, origin, zone_class, callback);
ifs.close();
}
void
masterLoad(istream& input, const Name& origin, const RRClass& zone_class,
MasterLoadCallback callback)
{
RRsetPtr rrset;
string line;
unsigned int line_count = 1;
do {
getline(input, line);
if (input.bad() || (input.fail() && !input.eof())) {
isc_throw(MasterLoadError, "Unexpectedly failed to read a line");
}
// blank/comment lines should be simply skipped.
if (line.empty() || line[0] == ';') {
continue;
}
// The line shouldn't have leading space (which means omitting the
// owner name).
if (isspace(line[0])) {
isc_throw(MasterLoadError, "Leading space at line " << line_count);
}
// Parse a single RR
istringstream iss(line);
string owner_txt, ttl_txt, rrclass_txt, rrtype_txt;
stringbuf rdatabuf;
iss >> owner_txt >> ttl_txt >> rrclass_txt >> rrtype_txt >> &rdatabuf;
if (iss.bad() || iss.fail()) {
isc_throw(MasterLoadError, "Parse failure for a valid RR at line "
<< line_count);
}
// This simple version doesn't support relative owner names with a
// separate origin.
if (owner_txt.empty() || *(owner_txt.end() - 1) != '.') {
isc_throw(MasterLoadError, "Owner name is not absolute at line "
<< line_count);
}
// XXX: this part is a bit tricky (and less efficient). We are going
// to validate the text for the RR parameters, and throw an exception
// if any of them is invalid by converting an underlying exception
// to MasterLoadError. To do that, we need to define the corresponding
// variables used for RRset construction outside the try-catch block,
// but we don't like to use a temporary variable with a meaningless
// initial value. So we define pointers outside the try block
// and allocate/initialize the actual objects within the block.
// To make it exception safe we use Boost.scoped_ptr.
scoped_ptr<const Name> owner;
scoped_ptr<const RRTTL> ttl;
scoped_ptr<const RRClass> rrclass;
scoped_ptr<const RRType> rrtype;
ConstRdataPtr rdata;
try {
owner.reset(new Name(owner_txt));
ttl.reset(new RRTTL(ttl_txt));
rrclass.reset(new RRClass(rrclass_txt));
rrtype.reset(new RRType(rrtype_txt));
rdata = createRdata(*rrtype, *rrclass, rdatabuf.str());
} catch (const Exception& ex) {
isc_throw(MasterLoadError, "Invalid RR text at line " << line_count
<< ": " << ex.what());
}
// Origin related validation:
// - reject out-of-zone data
// - reject SOA whose owner is not at the top of zone
const NameComparisonResult cmp_result = owner->compare(origin);
if (cmp_result.getRelation() != NameComparisonResult::EQUAL &&
cmp_result.getRelation() != NameComparisonResult::SUBDOMAIN) {
isc_throw(MasterLoadError, "Out-of-zone data at line "
<< line_count);
}
if (*rrtype == RRType::SOA() &&
cmp_result.getRelation() != NameComparisonResult::EQUAL) {
isc_throw(MasterLoadError, "SOA not at top of zone at line "
<< line_count);
}
// Reject RR class mismatching
if (*rrclass != zone_class) {
isc_throw(MasterLoadError, "RR class (" << rrclass_txt
<< ") does not match the zone class (" << zone_class
<< ") at line " << line_count);
}
// Everything is okay. Now create/update RRset with the new RR.
// If this is the first RR or the RR type/name is new, we are seeing
// a new RRset.
if (!rrset || rrset->getType() != *rrtype ||
rrset->getName() != *owner) {
// Commit the previous RRset, if any.
if (rrset) {
callback(rrset);
}
rrset = RRsetPtr(new RRset(*owner, *rrclass, *rrtype, *ttl));
}
rrset->addRdata(rdata);
} while (++line_count, !input.eof());
// Commit the last RRset, if any.
if (rrset) {
callback(rrset);
}
}
} // namespace dns
} // namespace isc
// Copyright (C) 2010 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 __MASTERLOAD_H
#define __MASTERLOAD_H 1
#include <iosfwd>
#include <boost/function.hpp>
#include <exceptions/exceptions.h>
#include <dns/rrset.h>
namespace isc {
namespace dns {
class Name;
class RRClass;
/// \brief An exception that is thrown if an error occurs while loading a
/// master zone data.
class MasterLoadError : public Exception {
public:
MasterLoadError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) {}
};
/// The type of the \c callback parameter of \c masterLoad().
///
/// This represents a functor object or a function that takes one parameter
/// of type \c RRsetPtr and returns nothing.
typedef boost::function<void(RRsetPtr rrset)> MasterLoadCallback;
///
/// \name Master zone file loader functions.
///
//@{
/// Master zone file loader from a file.
///
/// This function parses a given file as a master DNS zone file for
/// the given origin name and RR class, constructs a sequence of \c RRset
/// from the RRs containing in the file, and calls the given \c callback
/// functor object or function with each \c RRset.
///
/// The \c callback parameter is a functor object or a function that
/// takes one parameter of type \c RRsetPtr and returns nothing,
/// i.e. \c void (see below for specific examples).
/// More precisely, it can be anything that this form of boost::function
/// can represent, but the caller normally doesn't have to care about
/// that level of details.
///
/// The ownership of constructed RRsets is transferred to the callback
/// and this function never uses it once it is called.
/// The callback can freely modify the passed \c RRset.
///
/// This function performs minimum level of validation on the input:
/// - Each RR is a valid textual representation per the DNS protocol.
/// - The class of each RR must be identical to the specified RR class.
/// - The owner name of each RR must be a subdomain of the origin name
/// (that can be equal to the origin).
/// - If an SOA RR is included, its owner name must be the origin name.
/// If any of these validation checks fails, this function throws an
/// exception of class \c MasterLoadError.
///
/// It does not perform other semantical checks, however. For example,
/// it doesn't check if an NS RR of the origin name is included or if
/// there is more than one SOA RR. Such further checks are the caller's
/// (or the callback's) responsibility.
///
/// <b>Acceptable Format</b>
///
/// The current implementation only supports a restricted form of master files
/// for simplicity. One easy way to ensure that a handwritten zone file is
/// acceptable to this implementation is to preprocess it with BIND 9's
/// named-compilezone tool with both the input and output formats being
/// "text".
/// Here is an example:
/// \code % named-compilezone -f text -F text -o example.com.norm
/// example.com example.com.zone
/// \endcode
/// where example.com.zone is the original zone file for the "example.com"
/// zone. The output file is example.com.norm, which should be acceptable
/// by this implementation.
///
/// Below are specific restrictions that this implementation assumes.
/// Basically, each RR must consist of exactly one line
/// (so there shouldn't be a multi-line RR) in the following format:
/// \code <owner name> <TTL> <RRCLASS> <RRTYPE> <RDATA (single line)>
/// \endcode
/// Here are some more details about the restrictions:
/// - No special directives such as $TTL are supported.
/// - The owner name must be absolute, that is, it must end with a period.
/// - "@" is not recognized as a valid owner name.
/// - Owner names, TTL and RRCLASS cannot be omitted.
/// - As a corollary, a non blank line must not begin with a space character.
/// - The order of the RR parameters is fixed, for example, this is acceptable:
/// \code example.com. 3600 IN A 192.0.2.1
/// \endcode
/// but this is not even though it's valid per RFC1035:
/// \code example.com. IN 3600 A 192.0.2.1
/// \endcode
/// - <TTL>, <RRCLASS>, and <RRTYPE> must be recognizable by the \c RRTTL,
/// RRClass and RRType class implementations of this library. In particular,
/// as of this writing TTL must be a decimal number (a convenient extension
/// such as "1H" instead of 3600 cannot be used). Not all standard RR
/// classes and RR types are supported yet, so the mnemonics of them will
/// be rejected, too.
/// - RR TTLs of the same RRset must be the same; even if they are different,
/// this implementation simply uses the TTL of the first RR.
///
/// Blank lines and lines beginning with a semi-colon are allowed, and will
/// be simply ignored. Comments cannot coexist with an RR line, however.
/// For example, this will be rejected:
/// \code example.com. 3600 IN A 192.0.2.1 ; this is a comment
/// \endcode
///
/// This implementation assumes that RRs of a single RRset are not
/// interleaved with RRs of a different RRset.
/// That is, the following sequence shouldn't happen:
/// \code example.com. 3600 IN A 192.0.2.1
/// example.com. 3600 IN AAAA 2001:db8::1
/// example.com. 3600 IN A 192.0.2.2
/// \endcode
/// But it does not consider this an error; it will simply regard each RR
/// as a separate RRset and call the callback with them separately.
/// It is up to the callback to merge multiple RRsets into one if possible
/// and necessary.
///
/// <b>Exceptions</b>
///
/// This function throws an exception of class \c MasterLoadError in the
/// following cases:
/// - Any of the validation checks fails (see the class description).
/// - The input data is not in the acceptable format (see the details of
/// the format above).
/// - The specified file cannot be opened for loading.
/// - An I/O error occurs during the loading.
///
/// In addition, this function requires resource allocation for parsing and
/// constructing RRsets. If it fails, the corresponding standard exception
/// will be thrown.
///
/// The callback may throw its own function. This function doesn't catch it
/// and will simply propagate it towards the caller.
///
/// <b>Usage Examples</b>
///
/// A simplest example usage of this function would be to parse a zone
/// file and (after validation) dump the content to the standard output.
/// This is an example functor object and a call to \c masterLoad
/// that implements this scenario:
/// \code struct ZoneDumper {
/// void operator()(ConstRRsetPtr rrset) const {
/// std::cout << *rrset;
/// }
/// };
/// ...
/// masterLoad(zone_file, Name("example.com"), RRClass::IN(), ZoneDumper());
/// \endcode
/// Alternatively, you can use a normal function instead of a functor:
/// \code void zoneDumper(ConstRRsetPtr rrset) {
/// std::cout << *rrset;
/// }
/// ...
/// masterLoad(zone_file, Name("example.com"), RRClass::IN(), zoneDumper);
/// \endcode
/// Or, if you want to use it with a member function of some other class,
/// wrapping things with \c boost::bind would be handy:
/// \code class ZoneDumper {
/// public:
/// void dump(ConstRRsetPtr rrset) const {
/// std::cout << *rrset;
/// }
/// };
/// ...
/// ZoneDumper dumper;
/// masterLoad(rr_stream, Name("example.com"), RRClass::IN(),
/// boost::bind(&ZoneDumper::dump, &dumper, _1));
/// \endcode
/// You can find a bit more complicated examples in the unit tests code for
/// this function.
///
/// <b>Implementation Notes</b>
///
/// The current implementation is in a preliminary level and needs further
/// extensions. Some design decisions may also have to be reconsidered as
/// we gain experiences. Those include:
/// - We should be more flexible about the input format.
/// - We may want to allow optional conditions. For example, we may want to
/// be generous about some validation failures and be able to continue
/// parsing.
/// - Especially if we allow to be generous, we may also want to support
/// returning an error code instead of throwing an exception when we
/// encounter validation failure.
/// - We may want to support incremental loading.
/// - If we add these optional features we may want to introduce a class
/// that encapsulates loading status and options.
/// - RRSIGs are currently identified as their owner name and RR type (RRSIG).
/// In practice it should be sufficient, but technically we should also
/// consider the Type Covered field.
///
/// \param filename A path to a master zone file to be loaded.
/// \param origin The origin name of the zone.
/// \param zone_class The RR class of the zone.
/// \param callbck A callback functor or function that is to be called
/// for each RRset.
void masterLoad(const char* const filename, const Name& origin,
const RRClass& zone_class, MasterLoadCallback callback);
/// Master zone file loader from input stream.
///
/// This function is same as the other version
/// (\c masterLoad(const char* const, const Name&, const RRClass&, MasterLoadCallback))
/// except that it takes a \c std::istream instead of a file.
/// It extracts lines from the stream and handles each line just as a line
/// of a file for the other version of function.
/// All descriptions of the other version apply to this version except those
/// specific to file I/O.
///
/// \param input An input stream object that is to emit zone's RRs.
/// \param origin The origin name of the zone.
/// \param zone_class The RR class of the zone.
/// \param callbck A callback functor or function that is to be called for
/// each RRset.
void masterLoad(std::istream& input, const Name& origin,
const RRClass& zone_class, MasterLoadCallback callback);
}
//@}
}
#endif // __MASTERLOAD_H
// Local Variables:
// mode: c++
// End:
......@@ -42,6 +42,7 @@ run_unittests_SOURCES += rdata_tsig_unittest.cc
run_unittests_SOURCES += rrset_unittest.cc rrsetlist_unittest.cc
run_unittests_SOURCES += question_unittest.cc
run_unittests_SOURCES += rrparamregistry_unittest.cc
run_unittests_SOURCES += masterload_unittest.cc
run_unittests_SOURCES += message_unittest.cc
run_unittests_SOURCES += base32hex_unittest.cc
run_unittests_SOURCES += base64_unittest.cc
......
// Copyright (C) 2010 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 <functional>
#include <ios>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <boost/bind.hpp>
#include <gtest/gtest.h>
#include <dns/masterload.h>
#include <dns/name.h>
#include <dns/rrclass.h>
#include <dns/rrset.h>
using namespace std;
using namespace isc::dns;
namespace {
// A callback functor for masterLoad() commonly used for the following tests.
class TestCallback : public unary_function<ConstRRsetPtr, void> {
public:
TestCallback(vector<ConstRRsetPtr>& rrsets) : rrsets_(rrsets) {}
void operator()(ConstRRsetPtr rrset) {
rrsets_.push_back(rrset);
}
private:
vector<ConstRRsetPtr>& rrsets_;
};
// A function version of TestCallback.
void
testCallback(ConstRRsetPtr rrset, vector<ConstRRsetPtr>* rrsets) {
rrsets->push_back(rrset);
}
class MasterTest : public ::testing::Test {
protected:
MasterTest() : origin("example.com"), zclass(RRClass::IN()),
callback(results) {}
public:
void rrsetCallback(ConstRRsetPtr rrset) {
results.push_back(rrset);
}
protected:
Name origin;
RRClass zclass;
stringstream rr_stream;
vector<ConstRRsetPtr> results;
TestCallback callback;
};
// Commonly used test RRs
const char* const txt_rr = "example.com. 3600 IN TXT \"test data\"\n";
const char* const a_rr1 = "www.example.com. 60 IN A 192.0.2.1\n";
const char* const a_rr2 = "www.example.com. 60 IN A 192.0.2.2\n";
const char* const a_rr3 = "ftp.example.com. 60 IN A 192.0.2.3\n";
// multi-field RR case
const char* const soa_rr = "example.com. 7200 IN SOA . . 0 0 0 0 0\n";
TEST_F(MasterTest, loadRRs) {
// a simple case: loading 3 RRs, each consists of a single RRset.
rr_stream << txt_rr << a_rr1 << soa_rr;
masterLoad(rr_stream, origin, zclass, callback);
ASSERT_EQ(3, results.size());
EXPECT_EQ(txt_rr, results[0]->toText());
EXPECT_EQ(a_rr1, results[1]->toText());
EXPECT_EQ(soa_rr, results[2]->toText());
}
TEST_F(MasterTest, loadWithFunctionCallback) {
// The same test as loadRRs but using a normal function (not a functor
// object)
rr_stream << txt_rr << a_rr1 << soa_rr;
masterLoad(rr_stream, origin, zclass,
bind2nd(ptr_fun(testCallback), &results));
ASSERT_EQ(3, results.size());
EXPECT_EQ(txt_rr, results[0]->toText());
EXPECT_EQ(a_rr1, results[1]->toText());
EXPECT_EQ(soa_rr, results[2]->toText());
}
TEST_F(MasterTest, loadWithMemFunctionCallback) {
// The same test as loadRRs but using a class member function (with a
// help of Boost.bind)
rr_stream << txt_rr << a_rr1 << soa_rr;
masterLoad(rr_stream, origin, zclass,
boost::bind(&MasterTest::rrsetCallback, this, _1));
ASSERT_EQ(3, results.size());
EXPECT_EQ(txt_rr, results[0]->toText());
EXPECT_EQ(a_rr1, results[1]->toText());
EXPECT_EQ(soa_rr, results[2]->toText());
}
TEST_F(MasterTest, loadComments) {
rr_stream << ";; comment line, should be skipped\n"
<< "\n" // blank line (should be skipped)
<< txt_rr;
masterLoad(rr_stream, origin, zclass, callback);
ASSERT_EQ(1, results.size());
EXPECT_EQ(txt_rr, results[0]->toText());
}
TEST_F(MasterTest, loadRRset) {
// load an RRset containing two RRs
rr_stream << a_rr1 << a_rr2;
masterLoad(rr_stream, origin, zclass, callback);
ASSERT_EQ(1, results.size());
EXPECT_EQ(string(a_rr1) + string(a_rr2), results[0]->toText());
}
TEST_F(MasterTest, loadRRsetsOfSameType) {
// load two RRsets with the same RR type and different owner names.
// the loader must distinguish them as separate RRsets.
rr_stream << a_rr1 << a_rr3;
masterLoad(rr_stream, origin, zclass, callback);
ASSERT_EQ(2, results.size());
EXPECT_EQ(a_rr1, results[0]->toText());
EXPECT_EQ(a_rr3, results[1]->toText());
}
TEST_F(MasterTest, loadRRsetsInterleaved) {
// two RRs that belongs to the same RRset (rr1 and rr2) are interleaved
// by another. This is an unexpected case for this loader, but it's
// not considered an error. The loader will simply treat them separate
// RRsets.
rr_stream << a_rr1 << a_rr3 << a_rr2;
masterLoad(rr_stream, origin, zclass, callback);
ASSERT_EQ(3, results.size());
EXPECT_EQ(a_rr1, results[0]->toText());
EXPECT_EQ(a_rr3, results[1]->toText());
EXPECT_EQ(a_rr2, results[2]->toText());
}
TEST_F(MasterTest, loadWithNoEOF) {
// the input stream doesn't end with a new line (and the following blank
// line). It should be accepted.
string rr_string(a_rr1);
rr_string.erase(rr_string.end() - 1);
rr_stream << rr_string;
masterLoad(rr_stream, origin, zclass, callback);
ASSERT_EQ(1, results.size());
EXPECT_EQ(a_rr1, results[0]->toText());
}
TEST_F(MasterTest, loadEmpty) {
// an unusual case: empty input. load must succeed with an empty result.
masterLoad(rr_stream, origin, zclass, callback);
EXPECT_EQ(0, results.size());
}
TEST_F(MasterTest, loadWithBeginningSpace) {
rr_stream << " " << a_rr1;
EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
MasterLoadError);
}
TEST_F(MasterTest, loadWithBeginningTab) {
rr_stream << "\t" << a_rr1;
EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
MasterLoadError);
}
TEST_F(MasterTest, loadInvalidRRClass) {
rr_stream << "example.com. 3600 CH TXT \"test text\"";
EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
MasterLoadError);
}
TEST_F(MasterTest, loadOutOfZoneData) {
rr_stream << "example.org. 3600 IN A 192.0.2.255";
EXPECT_THROW(masterLoad(rr_stream, origin, zclass, callback),
MasterLoadError);
}
TEST_F(MasterTest, loadNonAtopSOA) {
// SOA's owner name must be zone's origin.
rr_stream << "soa.example.com. 3600 IN SOA . . 0 0 0 0 0";