diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am index c164a872b55349d5435bdd3f392771cb575f0d1d..dc87341a672637016844abb1794a7b06dae3906b 100644 --- a/src/lib/datasrc/Makefile.am +++ b/src/lib/datasrc/Makefile.am @@ -15,3 +15,4 @@ libdatasrc_la_SOURCES += static_datasrc.h static_datasrc.cc libdatasrc_la_SOURCES += sqlite3_datasrc.h sqlite3_datasrc.cc libdatasrc_la_SOURCES += query.h query.cc libdatasrc_la_SOURCES += cache.h cache.cc +libdatasrc_la_SOURCES += zonetable.h zonetable.cc diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am index 62cc7e943fb9a91ad245f9676a1d430302f2f45f..1fb03153e8c360adab4a8b9128f312b14bc5ce24 100644 --- a/src/lib/datasrc/tests/Makefile.am +++ b/src/lib/datasrc/tests/Makefile.am @@ -24,6 +24,7 @@ run_unittests_SOURCES += static_unittest.cc run_unittests_SOURCES += query_unittest.cc run_unittests_SOURCES += cache_unittest.cc run_unittests_SOURCES += test_datasrc.h test_datasrc.cc +run_unittests_SOURCES += zonetable_unittest.cc run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) run_unittests_LDADD = $(GTEST_LDADD) diff --git a/src/lib/datasrc/tests/zonetable_unittest.cc b/src/lib/datasrc/tests/zonetable_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..02b96b8cc4c0c5ee8d76e6a0598837961160c3eb --- /dev/null +++ b/src/lib/datasrc/tests/zonetable_unittest.cc @@ -0,0 +1,97 @@ +// 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 + +#include +#include + +#include + +#include + +using namespace isc::dns; +using namespace isc::datasrc; + +namespace { +TEST(ZoneTest, init) { + Zone zone(RRClass::IN(), Name("example.com")); + EXPECT_EQ(Name("example.com"), zone.getOrigin()); + EXPECT_EQ(RRClass::IN(), zone.getClass()); + + Zone ch_zone(RRClass::CH(), Name("example")); + EXPECT_EQ(Name("example"), ch_zone.getOrigin()); + EXPECT_EQ(RRClass::CH(), ch_zone.getClass()); +} + +class ZoneTableTest : public ::testing::Test { +protected: + ZoneTableTest() : zone1(new Zone(RRClass::IN(), Name("example.com"))), + zone2(new Zone(RRClass::IN(), Name("example.net"))), + zone3(new Zone(RRClass::IN(), Name("example"))) + {} + ZoneTable zone_table; + ZonePtr zone1, zone2, zone3; +}; + +TEST_F(ZoneTableTest, add) { + EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone1)); + EXPECT_EQ(ZoneTable::EXIST, zone_table.add(zone1)); + // names are compared in a case insensitive manner. + EXPECT_EQ(ZoneTable::EXIST, zone_table.add( + ZonePtr(new Zone(RRClass::IN(), Name("EXAMPLE.COM"))))); + + EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone2)); + EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone3)); + + // Zone table is indexed only by name. Duplicate origin name with + // different zone class isn't allowed. + EXPECT_EQ(ZoneTable::EXIST, zone_table.add( + ZonePtr(new Zone(RRClass::CH(), Name("example.com"))))); + + /// Bogus zone (NULL) + EXPECT_THROW(zone_table.add(ZonePtr()), isc::InvalidParameter); +} + +TEST_F(ZoneTableTest, remove) { + EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone1)); + EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone2)); + EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone3)); + + EXPECT_EQ(ZoneTable::SUCCESS, zone_table.remove(Name("example.net"))); + EXPECT_EQ(ZoneTable::NOTFOUND, zone_table.remove(Name("example.net"))); +} + +TEST_F(ZoneTableTest, find) { + EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone1)); + EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone2)); + EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone3)); + + EXPECT_EQ(ZoneTable::SUCCESS, zone_table.find(Name("example.com")).code); + EXPECT_EQ(Name("example.com"), + zone_table.find(Name("example.com")).zone->getOrigin()); + + EXPECT_EQ(ZoneTable::NOTFOUND, + zone_table.find(Name("example.org")).code); + EXPECT_EQ(static_cast(NULL), + zone_table.find(Name("example.org")).zone); + + // there's no exact match. the result should be the longest match, + // and the code should be PARTIALMATCH. + EXPECT_EQ(ZoneTable::PARTIALMATCH, + zone_table.find(Name("www.example.com")).code); + EXPECT_EQ(Name("example.com"), + zone_table.find(Name("www.example.com")).zone->getOrigin()); +} +} diff --git a/src/lib/datasrc/zonetable.cc b/src/lib/datasrc/zonetable.cc new file mode 100644 index 0000000000000000000000000000000000000000..97890ba234135642f404019fe5a2da6210c409bb --- /dev/null +++ b/src/lib/datasrc/zonetable.cc @@ -0,0 +1,112 @@ +// 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. + +// Note: map and utility (for 'pair') are for temporary workaround. +// we'll soon replace them with built-in intelligent backend structure. +#include +#include + +#include +#include + +#include + +using namespace std; +using namespace isc::dns; + +namespace isc { +namespace datasrc { + +struct Zone::ZoneImpl { + ZoneImpl(const RRClass& zone_class, const Name& origin) : + zone_class_(zone_class), origin_(origin) + {} + RRClass zone_class_; + Name origin_; +}; + +Zone::Zone(const RRClass& zone_class, const Name& origin) : impl_(NULL) { + impl_ = new ZoneImpl(zone_class, origin); +} + +Zone::~Zone() { + delete impl_; +} + +const Name& +Zone::getOrigin() const { + return (impl_->origin_); +} + +const RRClass& +Zone::getClass() const { + return (impl_->zone_class_); +} + +// This is a temporary, inefficient implementation using std::map and handmade +// iteration to realize longest match. + +struct ZoneTable::ZoneTableImpl { + typedef map ZoneMap; + typedef pair NameAndZone; + ZoneMap zones; +}; + +ZoneTable::ZoneTable() : impl_(new ZoneTableImpl) +{} + +ZoneTable::~ZoneTable() { + delete impl_; +} + +ZoneTable::Result +ZoneTable::add(ZonePtr zone) { + if (!zone) { + isc_throw(InvalidParameter, + "Null pointer is passed to ZoneTable::add()"); + } + + if (impl_->zones.insert( + ZoneTableImpl::NameAndZone(zone->getOrigin(), zone)).second + == true) { + return (SUCCESS); + } else { + return (EXIST); + } +} + +ZoneTable::Result +ZoneTable::remove(const Name& origin) { + return (impl_->zones.erase(origin) == 1 ? SUCCESS : NOTFOUND); +} + +ZoneTable::FindResult +ZoneTable::find(const Name& name) const { + Name qname(name); + + // Inefficient internal loop to find a longest match. + // This will be replaced with a single call to more intelligent backend. + for (int i = 0; i < name.getLabelCount(); ++i) { + Name matchname(name.split(i)); + ZoneTableImpl::ZoneMap::const_iterator found = + impl_->zones.find(matchname); + if (found != impl_->zones.end()) { + return (FindResult(i == 0 ? SUCCESS : PARTIALMATCH, + (*found).second.get())); + } + } + return (FindResult(NOTFOUND, NULL)); +} +} // end of namespace datasrc +} // end of namespace isc diff --git a/src/lib/datasrc/zonetable.h b/src/lib/datasrc/zonetable.h new file mode 100644 index 0000000000000000000000000000000000000000..e1af62621a2971c4ba4605949bf2453d479e84be --- /dev/null +++ b/src/lib/datasrc/zonetable.h @@ -0,0 +1,248 @@ +// 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 __ZONETABLE_H +#define __ZONETABLE_H 1 + +#include + +namespace isc { +namespace dns { +class Name; +class RRClass; +}; + +namespace datasrc { + +/// \brief A single authoritative zone +/// +/// The \c Zone class represents a DNS zone as part of %data source. +/// +/// At the moment this is provided mainly for making the \c ZoneTable class +/// testable, and only provides a minimal set of features. +/// This is why this class is defined in the same header file, but it may +/// have to move to a separate header file when we understand what is +/// necessary for this class for actual operation. +/// Likewise, it will have more features. For example, it will maintain +/// information about the location of a zone file, whether it's loaded in +/// memory, etc. +class Zone { + /// + /// \name Constructors and Destructor. + /// + /// \b Note: + /// The copy constructor and the assignment operator are intentionally + /// defined as private, making this class non copyable. + //@{ +private: + Zone(const Zone& source); + Zone& operator=(const Zone& source); +public: + /// \brief Constructor from zone parameters. + /// + /// This constructor internally involves resource allocation, and if + /// it fails, a corresponding standard exception will be thrown. + /// It never throws an exception otherwise. + /// + /// \param rrclass The RR class of the zone. + /// \param origin The origin name of the zone. + Zone(const isc::dns::RRClass& rrclass, const isc::dns::Name& origin); + + /// The destructor. + ~Zone(); + //@} + + /// + /// \name Getter Methods + /// + /// These methods never throw an exception. + //@{ + /// \brief Return the origin name of the zone. + const isc::dns::Name& getOrigin() const; + + /// \brief Return the RR class of the zone. + const isc::dns::RRClass& getClass() const; + //@} + +private: + struct ZoneImpl; + ZoneImpl* impl_; +}; + +/// \brief A pointer-like type pointing to a \c Zone object. +typedef boost::shared_ptr ZonePtr; + +/// \brief A pointer-like type pointing to a \c Zone object. +typedef boost::shared_ptr ConstZonePtr; + +/// \brief A set of authoritative zones. +/// +/// The \c ZoneTable class represents a set of zones of the same RR class +/// and provides a basic interface to help DNS lookup processing. +/// For a given domain name, its \c find() method searches the set for a zone +/// that gives a longest match against that name. +/// +/// The set of zones are assumed to be of the same RR class, but the +/// \c ZoneTable class does not enforce the assumption through its interface. +/// For example, the \c add() method does not check if the new zone +/// is of the same RR class as that of the others already in the table. +/// It is caller's responsibility to ensure this assumption. +/// +/// Notes to developer: +/// +/// The add() method takes a (Boost) shared pointer because it would be +/// inconvenient to require the caller to maintain the ownership of zones, +/// while it wouldn't be safe to delete unnecessary zones inside the zone +/// table. +/// +/// On the other hand, the find() method returns a bare pointer, rather than +/// the shared pointer, in order to minimize the dependency on Boost +/// definitions in our public interfaces. This means the caller can only +/// refer to the returned object (via the pointer) for a short period. +/// It should be okay for simple lookup purposes, but if we see the need +/// for keeping a \c Zone object for a longer period of context, we may +/// have to revisit this decision. +/// +/// Currently, \c FindResult::zone is immutable for safety. +/// In future versions we may want to make it changeable. For example, +/// we may want to allow configuration update on an existing zone. +/// +/// In BIND 9's "zt" module, the equivalent of \c find() has an "option" +/// parameter. The only defined option is the one to specify the "no exact" +/// mode, and the only purpose of that mode is to prefer a second longest match +/// even if there is an exact match in order to deal with type DS query. +/// This trick may help enhance performance, but it also seems to make the +/// implementation complicated for a very limited, minor case. So, for now, +/// we don't introduce the special mode, and, since it was the only reason to +/// have search options in BIND 9, our initial implementation doesn't provide +/// a switch for options. +class ZoneTable { +public: + /// Result codes of various public methods of \c ZoneTable. + /// + /// The detailed semantics may differ in different methods. + /// See the description of specific methods for more details. + enum Result { + SUCCESS, ///< The operation is successful. + EXIST, ///< A zone is already stored in \c ZoneTable. + NOTFOUND, ///< The specified zone is not found in \c ZoneTable. + PARTIALMATCH ///< \c Only a partial match is found in \c find(). + }; + + /// \brief A helper structure to represent the search result of + /// ZoneTable::find(). + /// + /// This is a straightforward pair of the result code and a pointer + /// to the found zone to represent the result of \c find(). + /// We use this in order to avoid overloading the return value for both + /// the result code ("success" or "not found") and the found object, + /// i.e., avoid using \c NULL to mean "not found", etc. + /// + /// This is a simple value class with no internal state, so for + /// convenience we allow the applications to refer to the members + /// directly. + /// + /// See the description of \c find() for the semantics of the member + /// variables. + struct FindResult { + FindResult(Result param_code, const Zone* param_zone) : + code(param_code), zone(param_zone) + {} + const Result const code; + const Zone* const zone; + }; + + /// + /// \name Constructors and Destructor. + /// + /// \b Note: + /// The copy constructor and the assignment operator are intentionally + /// defined as private, making this class non copyable. + //@{ +private: + ZoneTable(const ZoneTable& source); + ZoneTable& operator=(const ZoneTable& source); + +public: + /// Default constructor. + /// + /// This constructor internally involves resource allocation, and if + /// it fails, a corresponding standard exception will be thrown. + /// It never throws an exception otherwise. + ZoneTable(); + + /// The destructor. + ~ZoneTable(); + //@} + + /// Add a \c Zone to the \c ZoneTable. + /// + /// \c zone must not be associated with a NULL pointer; otherwise + /// an exception of class \c InvalidParameter will be thrown. + /// If internal resource allocation fails, a corresponding standard + /// exception will be thrown. + /// This method never throws an exception otherwise. + /// + /// \param zone A \c Zone object to be added. + /// \return \c SUCCESS If the zone is successfully added to the zone table. + /// \return \c EXIST The zone table already stores a zone that has the + /// same origin. + Result add(ZonePtr zone); + + /// Remove a \c Zone of the given origin name from the \c ZoneTable. + /// + /// This method never throws an exception. + /// + /// \param origin The origin name of the zone to be removed. + /// \return \c SUCCESS If the zone is successfully removed from the + /// zone table. + /// \return \c NOTFOUND The zone table does not store the zone that matches + /// \c origin. + Result remove(const isc::dns::Name& origin); + + /// Find a \c Zone that best matches the given name in the \c ZoneTable. + /// + /// It searches the internal storage for a \c Zone that gives the + /// longest match against \c origin, and returns the result in the + /// form of a \c FindResult object as follows: + /// - \c code: The result code of the operation. + /// - \c SUCCESS: A zone that gives an exact match is found + /// - \c PARTIALMATCH: A zone whose origin is a super domain of + /// \c zone is found (but there is no exact match) + /// - \c NOTFOUND: For all other cases. + /// - \c zone: A pointer to the found \c Zone object if one is found; + /// otherwise \c NULL. + /// + /// The pointer returned in the \c FindResult object is only valid until + /// the corresponding zone is removed from the zone table. + /// The caller must ensure that the zone is held in the zone table while + /// it needs to refer to it. + /// + /// This method never throws an exception. + /// + /// \param name A domain name for which the search is performed. + /// \return A \c FindResult object enclosing the search result (see above). + FindResult find(const isc::dns::Name& name) const; + +private: + struct ZoneTableImpl; + ZoneTableImpl* impl_; +}; +} +} +#endif // __ZONETABLE_H + +// Local Variables: +// mode: c++ +// End: