Commit 655395cd authored by JINMEI Tatuya's avatar JINMEI Tatuya

[master] Merge branch 'trac2429'

With fixing conflicts:
	src/lib/dns/master_loader.cc
	src/lib/dns/tests/master_loader_unittest.cc
parents ea8e70a3 93cbc6cc
......@@ -15,11 +15,14 @@
#include <dns/master_loader.h>
#include <dns/master_lexer.h>
#include <dns/name.h>
#include <dns/rdataclass.h>
#include <dns/rrttl.h>
#include <dns/rrclass.h>
#include <dns/rrtype.h>
#include <dns/rdata.h>
#include <boost/scoped_ptr.hpp>
#include <string>
#include <memory>
#include <boost/algorithm/string/predicate.hpp> // for iequals
......@@ -64,23 +67,10 @@ public:
ok_(true),
many_errors_((options & MANY_ERRORS) != 0),
complete_(false),
seen_error_(false)
seen_error_(false),
warn_rfc1035_ttl_(true)
{}
void reportError(const std::string& filename, size_t line,
const std::string& reason)
{
seen_error_ = true;
callbacks_.error(filename, line, reason);
if (!many_errors_) {
// In case we don't have the lenient mode, every error is fatal
// and we throw
ok_ = false;
complete_ = true;
isc_throw(MasterLoaderError, reason.c_str());
}
}
void pushSource(const std::string& filename) {
std::string error;
if (!lexer_.pushSource(filename.c_str(), &error)) {
......@@ -95,6 +85,28 @@ public:
initialized_ = true;
}
void pushStreamSource(std::istream& stream) {
lexer_.pushSource(stream);
initialized_ = true;
}
bool loadIncremental(size_t count_limit);
private:
void reportError(const std::string& filename, size_t line,
const std::string& reason)
{
seen_error_ = true;
callbacks_.error(filename, line, reason);
if (!many_errors_) {
// In case we don't have the lenient mode, every error is fatal
// and we throw
ok_ = false;
complete_ = true;
isc_throw(MasterLoaderError, reason.c_str());
}
}
bool popSource() {
if (lexer_.getSourceCount() == 1) {
return (false);
......@@ -103,19 +115,12 @@ public:
return (true);
}
void pushStreamSource(std::istream& stream) {
lexer_.pushSource(stream);
initialized_ = true;
}
// Get a string token. Handle it as error if it is not string.
const string getString() {
lexer_.getNextToken(MasterToken::STRING).getString(string_token_);
return (string_token_);
}
bool loadIncremental(size_t count_limit);
void doInclude() {
// First, get the filename to include
const string
......@@ -138,6 +143,107 @@ public:
pushSource(filename);
}
// Upper limit check when recognizing a specific TTL value from the
// zone file ($TTL, the RR's TTL field, or the SOA minimum). RFC2181
// Section 8 limits the range of TTL values to 2^31-1 (0x7fffffff),
// and prohibits transmitting a TTL field exceeding this range. We
// guarantee that by limiting the value at the time of zone
// parsing/loading, following what BIND 9 does. Resetting it to 0
// at this point may not be exactly what the RFC states (depending on
// the meaning of 'received'), but the end result would be the same (i.e.,
// the guarantee on transmission). Again, we follow the BIND 9's behavior
// here.
//
// post_parsing is true iff this method is called after parsing the entire
// RR and the lexer is positioned at the next line. It's just for
// calculating the accurate source line when callback is necessary.
void limitTTL(RRTTL& ttl, bool post_parsing) {
if (ttl > RRTTL::MAX()) {
const size_t src_line = lexer_.getSourceLine() -
(post_parsing ? 1 : 0);
callbacks_.warning(lexer_.getSourceName(), src_line,
"TTL " + ttl.toText() + " > MAXTTL, "
"setting to 0 per RFC2181");
ttl = RRTTL(0);
}
}
// Set/reset the default TTL. This should be from either $TTL or SOA
// minimum TTL (it's the caller's responsibility; this method doesn't
// care about where it comes from). see LimitTTL() for parameter
// post_parsing.
void setDefaultTTL(const RRTTL& ttl, bool post_parsing) {
if (!default_ttl_) {
default_ttl_.reset(new RRTTL(ttl));
} else {
*default_ttl_ = ttl;
}
limitTTL(*default_ttl_, post_parsing);
}
// Try to set/reset the current TTL from candidate TTL text. It's possible
// it does not actually represent a TTL (which is not immediately
// considered an error). Return true iff it's recognized as a valid TTL
// (and only in which case the current TTL is set).
bool setCurrentTTL(const string& ttl_txt) {
// We use the factory version instead of RRTTL constructor as we
// need to expect cases where ttl_txt does not actually represent a TTL
// but an RR class or type.
const MaybeRRTTL maybe_ttl = RRTTL::createFromText(ttl_txt);
if (maybe_ttl) {
current_ttl_ = maybe_ttl;
limitTTL(*current_ttl_, false);
return (true);
}
return (false);
}
// Determine the TTL of the current RR based on the given parsing context.
//
// explicit_ttl is true iff the TTL is explicitly specified for that RR
// (in which case current_ttl_ is set to that TTL).
// rrtype is the type of the current RR, and rdata is its RDATA. They
// only matter if the type is SOA and no available TTL is known. In this
// case the minimum TTL of the SOA will be used as the TTL of that SOA
// and the default TTL for subsequent RRs.
const RRTTL& getCurrentTTL(bool explicit_ttl, const RRType& rrtype,
const rdata::ConstRdataPtr& rdata) {
// We've completed parsing the full of RR, and the lexer is already
// positioned at the next line. If we need to call callback,
// we need to adjust the line number.
const size_t current_line = lexer_.getSourceLine() - 1;
if (!current_ttl_ && !default_ttl_) {
if (rrtype == RRType::SOA()) {
callbacks_.warning(lexer_.getSourceName(), current_line,
"no TTL specified; "
"using SOA MINTTL instead");
const uint32_t ttl_val =
dynamic_cast<const rdata::generic::SOA&>(*rdata).
getMinimum();
setDefaultTTL(RRTTL(ttl_val), true);
current_ttl_ = *default_ttl_;
} else {
// On catching the exception we'll try to reach EOL again,
// so we need to unget it now.
lexer_.ungetToken();
throw InternalException(__FILE__, __LINE__,
"no TTL specified; load rejected");
}
} else if (!explicit_ttl && default_ttl_) {
current_ttl_ = *default_ttl_;
} else if (!explicit_ttl && warn_rfc1035_ttl_) {
// Omitted (class and) TTL values are default to the last
// explicitly stated values (RFC 1035, Sec. 5.1).
callbacks_.warning(lexer_.getSourceName(), current_line,
"using RFC1035 TTL semantics; default to the "
"last explicitly stated TTL");
warn_rfc1035_ttl_ = false; // we only warn about this once
}
assert(current_ttl_);
return (*current_ttl_);
}
void handleDirective(const char* directive, size_t length) {
if (iequals(directive, "INCLUDE")) {
doInclude();
......@@ -146,9 +252,8 @@ public:
isc_throw(isc::NotImplemented,
"Origin directive not implemented yet");
} else if (iequals(directive, "TTL")) {
// TODO: Implement
isc_throw(isc::NotImplemented,
"TTL directive not implemented yet");
setDefaultTTL(RRTTL(getString()), false);
eatUntilEOL(true);
} else {
isc_throw(InternalException, "Unknown directive '" <<
string(directive, directive + length) << "'");
......@@ -163,7 +268,7 @@ public:
case MasterToken::END_OF_FILE:
callbacks_.warning(lexer_.getSourceName(),
lexer_.getSourceLine(),
"File does not end with newline");
"Unexpected end of file");
// We don't pop here. The End of file will stay there,
// and we'll handle it in the next iteration of
// loadIncremental properly.
......@@ -190,6 +295,11 @@ private:
const RRClass zone_class_;
MasterLoaderCallbacks callbacks_;
AddRRCallback add_callback_;
boost::scoped_ptr<RRTTL> default_ttl_; // Default TTL of RRs used when
// unspecified. If NULL no default
// is known.
MaybeRRTTL current_ttl_; // The TTL used most recently. Initially unset.
// Once set always stores a valid RRTTL.
const MasterLoader::Options options_;
const std::string master_file_;
std::string string_token_;
......@@ -201,6 +311,8 @@ public:
bool complete_; // All work done.
bool seen_error_; // Was there at least one error during the
// load?
bool warn_rfc1035_ttl_; // should warn if implicit TTL determination
// from the previous RR is used.
};
bool
......@@ -260,8 +372,18 @@ MasterLoader::MasterLoaderImpl::loadIncremental(size_t count_limit) {
// anything yet
// The parameters
const RRTTL ttl(getString());
const RRClass rrclass(getString());
MasterToken rrparam_token = lexer_.getNextToken();
bool explicit_ttl = false;
if (rrparam_token.getType() == MasterToken::STRING) {
// Try TTL
if (setCurrentTTL(rrparam_token.getString())) {
explicit_ttl = true;
rrparam_token = lexer_.getNextToken();
}
}
const RRClass rrclass(rrparam_token.getString());
const RRType rrtype(getString());
// TODO: Some more validation?
......@@ -273,7 +395,7 @@ MasterLoader::MasterLoaderImpl::loadIncremental(size_t count_limit) {
}
// TODO: Check if it is SOA, it should be at the origin.
const rdata::RdataPtr data(rdata::createRdata(rrtype, rrclass,
const rdata::RdataPtr rdata(rdata::createRdata(rrtype, rrclass,
lexer_,
&zone_origin_,
options_,
......@@ -282,8 +404,10 @@ MasterLoader::MasterLoaderImpl::loadIncremental(size_t count_limit) {
// the Rdata. The errors should have been reported by
// callbacks_ already. We need to decide if we want to continue
// or not.
if (data) {
add_callback_(name, rrclass, rrtype, ttl, data);
if (rdata) {
add_callback_(name, rrclass, rrtype,
getCurrentTTL(explicit_ttl, rrtype, rdata),
rdata);
// Good, we loaded another one
++count;
......
......@@ -16,6 +16,7 @@
#include <string>
#include <boost/static_assert.hpp>
#include <boost/lexical_cast.hpp>
#include <exceptions/exceptions.h>
......@@ -112,6 +113,16 @@ SOA::getSerial() const {
return (Serial(b.readUint32()));
}
uint32_t
SOA::getMinimum() const {
// Make sure the buffer access is safe.
BOOST_STATIC_ASSERT(sizeof(numdata_) ==
sizeof(uint32_t) * 4 + sizeof(uint32_t));
InputBuffer b(&numdata_[sizeof(uint32_t) * 4], sizeof(uint32_t));
return (b.readUint32());
}
string
SOA::toText() const {
InputBuffer b(numdata_, sizeof(numdata_));
......
......@@ -35,8 +35,12 @@ public:
SOA(const Name& mname, const Name& rname, uint32_t serial,
uint32_t refresh, uint32_t retry, uint32_t expire,
uint32_t minimum);
/// \brief Returns the serial stored in the SOA.
Serial getSerial() const;
/// brief Returns the minimum TTL field value of the SOA.
uint32_t getMinimum() const;
private:
/// Note: this is a prototype version; we may reconsider
/// this representation later.
......
......@@ -57,9 +57,14 @@ Unit units[] = {
namespace isc {
namespace dns {
RRTTL::RRTTL(const std::string& ttlstr) {
namespace {
bool
parseTTLString(const string& ttlstr, uint32_t& ttlval, string* error_txt) {
if (ttlstr.empty()) {
isc_throw(InvalidRRTTL, "Empty TTL string");
if (error_txt != NULL) {
*error_txt = "Empty TTL string";
}
return (false);
}
// We use a larger data type during the computation. This is because
// some compilers don't fail when out of range, so we check the range
......@@ -80,8 +85,10 @@ RRTTL::RRTTL(const std::string& ttlstr) {
if (unit == end) {
if (units_mode) {
// We had some units before. The last one is missing unit.
isc_throw(InvalidRRTTL, "Missing the last unit: " <<
ttlstr);
if (error_txt != NULL) {
*error_txt = "Missing the last unit: " + ttlstr;
}
return (false);
} else {
// Case without any units at all. Just convert and store
// it.
......@@ -102,12 +109,18 @@ RRTTL::RRTTL(const std::string& ttlstr) {
}
}
if (!found) {
isc_throw(InvalidRRTTL, "Unknown unit used: " << *unit <<
" in: " << ttlstr);
if (error_txt != NULL) {
*error_txt = "Unknown unit used: " +
boost::lexical_cast<string>(*unit) + " in: " + ttlstr;
}
return (false);
}
// Now extract the number.
if (unit == pos) {
isc_throw(InvalidRRTTL, "Missing number in TTL: " << ttlstr);
if (error_txt != NULL) {
*error_txt = "Missing number in TTL: " + ttlstr;
}
return (false);
}
const int64_t value = boost::lexical_cast<int64_t>(string(pos,
unit));
......@@ -118,21 +131,48 @@ RRTTL::RRTTL(const std::string& ttlstr) {
// there's no need to continue).
if (value < 0 || value > 0xffffffff || val < 0 ||
val > 0xffffffff) {
isc_throw(InvalidRRTTL, "Part of TTL out of range: " <<
ttlstr);
if (error_txt != NULL) {
*error_txt = "Part of TTL out of range: " + ttlstr;
}
return (false);
}
// Move to after the unit.
pos = unit + 1;
}
} catch (const boost::bad_lexical_cast&) {
isc_throw(InvalidRRTTL, "invalid TTL: " << ttlstr);
if (error_txt != NULL) {
*error_txt = "invalid TTL: " + ttlstr;
}
return (false);
}
if (val >= 0 && val <= 0xffffffff) {
ttlval_ = val;
ttlval = val;
} else {
isc_throw(InvalidRRTTL, "TTL out of range: " << ttlstr);
if (error_txt != NULL) {
*error_txt = "TTL out of range: " + ttlstr;
}
return (false);
}
return (true);
}
}
RRTTL::RRTTL(const std::string& ttlstr) {
string error_txt;
if (!parseTTLString(ttlstr, ttlval_, &error_txt)) {
isc_throw(InvalidRRTTL, error_txt);
}
}
MaybeRRTTL
RRTTL::createFromText(const string& ttlstr) {
uint32_t ttlval;
if (parseTTLString(ttlstr, ttlval, NULL)) {
return (MaybeRRTTL(ttlval));
}
return (MaybeRRTTL());
}
RRTTL::RRTTL(InputBuffer& buffer) {
......
......@@ -15,10 +15,12 @@
#ifndef RRTTL_H
#define RRTTL_H 1
#include <stdint.h>
#include <exceptions/exceptions.h>
#include <boost/optional.hpp>
#include <stdint.h>
namespace isc {
namespace util {
class InputBuffer;
......@@ -30,6 +32,16 @@ namespace dns {
// forward declarations
class AbstractMessageRenderer;
class RRTTL; // forward declaration to define MaybeRRTTL
/// \brief A shortcut for a compound type to represent RRTTL-or-not.
///
/// A value of this type can be interpreted in a boolean context, whose
/// value is \c true if and only if it contains a valid RRTTL object.
/// And, if it contains a valid RRTTL object, its value is accessible
/// using \c operator*, just like a bare pointer to \c RRTTL.
typedef boost::optional<RRTTL> MaybeRRTTL;
///
/// \brief A standard DNS module exception that is thrown if an RRTTL object
/// is being constructed from an unrecognized string.
......@@ -61,7 +73,7 @@ public:
class RRTTL {
public:
///
/// \name Constructors and Destructor
/// \name Constructors, Factory and Destructor
///
/// Note: We use the default copy constructor and the default copy
/// assignment operator intentionally.
......@@ -72,6 +84,7 @@ public:
///
/// \param ttlval An 32-bit integer of the RRTTL.
explicit RRTTL(uint32_t ttlval) : ttlval_(ttlval) {}
/// Constructor from a string.
///
/// It accepts either a decimal number, specifying number of seconds. Or,
......@@ -87,6 +100,7 @@ public:
/// \throw InvalidRRTTL in case the string is not recognized as valid
/// TTL representation.
explicit RRTTL(const std::string& ttlstr);
/// Constructor from wire-format data.
///
/// The \c buffer parameter normally stores a complete DNS message
......@@ -98,6 +112,39 @@ public:
///
/// \param buffer A buffer storing the wire format data.
explicit RRTTL(isc::util::InputBuffer& buffer);
/// A separate factory of RRTTL from text.
///
/// This static method is similar to the constructor that takes a string
/// object, but works as a factory and reports parsing failure in the
/// form of the return value. Normally the constructor version should
/// suffice, but in some cases the caller may have to expect mixture of
/// valid and invalid input, and may want to minimize the overhead of
/// possible exception handling. This version is provided for such
/// purpose.
///
/// If the given text represents a valid RRTTL, it returns a \c MaybeRRTTL
/// object that stores a corresponding \c RRTTL object, which is
/// accessible via \c operator*(). In this case the returned object will
/// be interpreted as \c true in a boolean context. If the given text
/// does not represent a valid RRTTL, it returns a \c MaybeRRTTL object
/// which is interpreted as \c false in a boolean context.
///
/// One main purpose of this function is to minimize the overhead
/// when the given text does not represent a valid RR TTL. For this
/// reason this function intentionally omits the capability of delivering
/// details reason for the parse failure, such as in the \c want()
/// string when exception is thrown from the constructor (it will
/// internally require a creation of string object, which is relatively
/// expensive). If such detailed information is necessary, the constructor
/// version should be used to catch the resulting exception.
///
/// This function never throws the \c InvalidRRTTL exception.
///
/// \param ttlstr A string representation of the \c RRTTL.
/// \return An MaybeRRTTL object either storing an RRTTL object for
/// the given text or a \c false value.
static MaybeRRTTL createFromText(const std::string& ttlstr);
///
//@}
......@@ -236,6 +283,22 @@ public:
{ return (ttlval_ > other.ttlval_); }
//@}
///
/// \name Protocol constants
///
//@{
/// \brief The TTL of the max allowable value, per RFC2181 Section 8.
///
/// The max value is the largest unsigned 31 bit integer, 2^31-1.
///
/// \note At the moment an RRTTL object can have a value larger than
/// this limit. We may revisit it in a future version.
static const RRTTL& MAX() {
static const RRTTL max_ttl(0x7fffffff);
return (max_ttl);
}
//@}
private:
uint32_t ttlval_;
};
......
......@@ -17,11 +17,14 @@
#include <dns/rrtype.h>
#include <dns/rrset.h>
#include <dns/rrclass.h>
#include <dns/rrttl.h>
#include <dns/name.h>
#include <dns/rdata.h>
#include <gtest/gtest.h>
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/scoped_ptr.hpp>
#include <string>
......@@ -35,6 +38,7 @@ using std::string;
using std::list;
using std::stringstream;
using std::endl;
using boost::lexical_cast;
namespace {
class MasterLoaderTest : public ::testing::Test {
......@@ -102,7 +106,8 @@ public:
// Check the next RR in the ones produced by the loader
// Other than passed arguments are checked to be the default for the tests
void checkRR(const string& name, const RRType& type, const string& data) {
void checkRR(const string& name, const RRType& type, const string& data,
const RRTTL& rrttl = RRTTL(3600)) {
ASSERT_FALSE(rrsets_.empty());
RRsetPtr current = rrsets_.front();
rrsets_.pop_front();
......@@ -110,6 +115,7 @@ public:
EXPECT_EQ(Name(name), current->getName());
EXPECT_EQ(type, current->getType());
EXPECT_EQ(RRClass::IN(), current->getClass());
EXPECT_EQ(rrttl, current->getTTL());
ASSERT_EQ(1, current->getRdataCount());
EXPECT_EQ(0, isc::dns::rdata::createRdata(type, RRClass::IN(), data)->
compare(current->getRdataIterator()->getCurrent()));
......@@ -267,31 +273,53 @@ TEST_F(MasterLoaderTest, invalidFile) {
struct ErrorCase {
const char* const line; // The broken line in master file
const char* const reason; // If non NULL, the reason string
const char* const problem; // Description of the problem for SCOPED_TRACE
} const error_cases[] = {
{ "www... 3600 IN A 192.0.2.1", "Invalid name" },
{ "www FORTNIGHT IN A 192.0.2.1", "Invalid TTL" },
{ "www 3600 XX A 192.0.2.1", "Invalid class" },
{ "www 3600 IN A bad_ip", "Invalid Rdata" },
{ "www 3600 IN", "Unexpected EOLN" },
{ "www 3600 CH TXT nothing", "Class mismatch" },
{ "www \"3600\" IN A 192.0.2.1", "Quoted TTL" },
{ "www 3600 \"IN\" A 192.0.2.1", "Quoted class" },
{ "www 3600 IN \"A\" 192.0.2.1", "Quoted type" },
{ "unbalanced)paren 3600 IN A 192.0.2.1", "Token error 1" },
{ "www 3600 unbalanced)paren A 192.0.2.1", "Token error 2" },
{ "www... 3600 IN A 192.0.2.1", NULL, "Invalid name" },
{ "www FORTNIGHT IN A 192.0.2.1", NULL, "Invalid TTL" },
{ "www 3600 XX A 192.0.2.1", NULL, "Invalid class" },
{ "www 3600 IN A bad_ip", NULL, "Invalid Rdata" },
{ "www 3600 IN", NULL, "Unexpected EOLN" },
{ "www 3600 CH TXT nothing", NULL, "Class mismatch" },
{ "www \"3600\" IN A 192.0.2.1", NULL, "Quoted TTL" },
{ "www 3600 \"IN\" A 192.0.2.1", NULL, "Quoted class" },
{ "www 3600 IN \"A\" 192.0.2.1", NULL, "Quoted type" },
{ "unbalanced)paren 3600 IN A 192.0.2.1", NULL, "Token error 1" },
{ "www 3600 unbalanced)paren A 192.0.2.1", NULL,
"Token error 2" },
// Check the unknown directive. The rest looks like ordinary RR,
// so we see the $ is actually special.
{ "$UNKNOWN 3600 IN A 192.0.2.1", "Unknown $ directive" },
{ "$INCLUD " TEST_DATA_SRCDIR "/example.org", "Include too short" },
{ "$INCLUDES " TEST_DATA_SRCDIR "/example.org", "Include too long" },
{ "$INCLUDE", "Missing include path" },
{ "$INCLUDE /file/not/found", "Include file not found" },
{ "$INCLUDE /file/not/found and here goes bunch of garbage",
{ "$UNKNOWN 3600 IN A 192.0.2.1", NULL, "Unknown $ directive" },
{ "$INCLUD " TEST_DATA_SRCDIR "/example.org", NULL, "Include too short" },
{ "$INCLUDES " TEST_DATA_SRCDIR "/example.org", NULL, "Include too long" },
{ "$INCLUDE", NULL, "Missing include path" },
{ "$INCLUDE /file/not/found", NULL, "Include file not found" },
{ "$INCLUDE /file/not/found and here goes bunch of garbage", NULL,
"Include file not found and garbage at the end of line" },
{ NULL, NULL }
{ "$TTL 100 extra-garbage", "Extra tokens at the end of line",
"$TTL with extra token" },
{ "$TTL", "unexpected end of input", "missing TTL" },
{ "$TTL No-ttl", "Unknown unit used: N in: No-ttl", "bad TTL" },
{ "$TTL \"100\"", "invalid TTL: \"100\"", "bad TTL, quoted" },
{ "$TT 100", "Unknown directive 'TT'", "bad directive, too short" },
{ "$TTLLIKE 100", "Unknown directive 'TTLLIKE'", "bad directive, extra" },
{ NULL, NULL, NULL }
};
// A commonly used helper to check callback message.
void
checkCallbackMessage(const string& actual_msg, const string& expected_msg,
size_t expected_line) {
// The actual message should begin with the expected message.
EXPECT_EQ(0, actual_msg.find(expected_msg)) << "actual message: "
<< actual_msg;
// and it should end with "...:<line_num>]"
const string line_desc = ":" + lexical_cast<string>(expected_line) + "]";
EXPECT_EQ(actual_msg.size() - line_desc.size(), actual_msg.find(line_desc));
}
// Test a broken zone is handled properly. We test several problems,
// both in strict and lenient mode.
TEST_F(MasterLoaderTest, brokenZone) {
......@@ -309,6 +337,9 @@ TEST_F(MasterLoaderTest, brokenZone) {