Browse Source

[master] Merge branch 'trac2429'

With fixing conflicts:
	src/lib/dns/master_loader.cc
	src/lib/dns/tests/master_loader_unittest.cc
JINMEI Tatuya 12 years ago
parent
commit
655395cdae

+ 159 - 35
src/lib/dns/master_loader.cc

@@ -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,17 +395,19 @@ 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,
-                                                          lexer_,
-                                                          &zone_origin_,
-                                                          options_,
-                                                          callbacks_));
+            const rdata::RdataPtr rdata(rdata::createRdata(rrtype, rrclass,
+                                                           lexer_,
+                                                           &zone_origin_,
+                                                           options_,
+                                                           callbacks_));
             // In case we get NULL, it means there was error creating
             // 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;

+ 11 - 0
src/lib/dns/rdata/generic/soa_6.cc

@@ -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_));

+ 4 - 0
src/lib/dns/rdata/generic/soa_6.h

@@ -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.

+ 52 - 12
src/lib/dns/rrttl.cc

@@ -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) {

+ 66 - 3
src/lib/dns/rrttl.h

@@ -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_;
 };

+ 202 - 19
src/lib/dns/tests/master_loader_unittest.cc

@@ -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) {
             EXPECT_THROW(loader_->load(), MasterLoaderError);
             EXPECT_FALSE(loader_->loadedSucessfully());
             EXPECT_EQ(1, errors_.size());
+            if (ec->reason != NULL) {
+                checkCallbackMessage(errors_.at(0), ec->reason, 2);
+            }
             EXPECT_TRUE(warnings_.empty());
 
             checkRR("example.org", RRType::SOA(), "ns1.example.org. "
@@ -379,6 +410,158 @@ TEST_F(MasterLoaderTest, includeWithGarbage) {
     checkRR("www.example.org", RRType::AAAA(), "2001:db8::1");
 }
 
+// Test for "$TTL"
+TEST_F(MasterLoaderTest, ttlDirective) {
+    stringstream zone_stream;
+
+    // Set the default TTL with $TTL followed by an RR omitting the TTL
+    zone_stream << "$TTL 1800\nexample.org. IN A 192.0.2.1\n";
+    // $TTL can be quoted.  Also testing the case of $TTL being changed.
+    zone_stream << "\"$TTL\" 100\na.example.org. IN A 192.0.2.2\n";
+    // Extended TTL form is accepted.
+    zone_stream << "$TTL 1H\nb.example.org. IN A 192.0.2.3\n";
+    // Matching is case insensitive.
+    zone_stream << "$tTl 360\nc.example.org. IN A 192.0.2.4\n";
+    // Maximum allowable TTL
+    zone_stream << "$TTL 2147483647\nd.example.org. IN A 192.0.2.5\n";
+
+    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+              MasterLoader::DEFAULT);
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    checkRR("example.org", RRType::A(), "192.0.2.1", RRTTL(1800));
+    checkRR("a.example.org", RRType::A(), "192.0.2.2", RRTTL(100));
+    checkRR("b.example.org", RRType::A(), "192.0.2.3", RRTTL(3600));
+    checkRR("c.example.org", RRType::A(), "192.0.2.4", RRTTL(360));
+    checkRR("d.example.org", RRType::A(), "192.0.2.5", RRTTL(2147483647));
+}
+
+TEST_F(MasterLoaderTest, ttlFromSOA) {
+    // No $TTL, and the SOA doesn't have an explicit TTL field.  Its minimum
+    // TTL field will be used as the RR's TTL, and it'll be used as the
+    // default TTL for others.
+    stringstream zone_stream("example.org. IN SOA . . 0 0 0 0 1800\n"
+                             "a.example.org. IN A 192.0.2.1\n");
+    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+              MasterLoader::DEFAULT);
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    checkRR("example.org", RRType::SOA(), ". . 0 0 0 0 1800", RRTTL(1800));
+    checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(1800));
+
+    // The use of SOA minimum TTL should have caused a warning.
+    EXPECT_EQ(1, warnings_.size());
+    checkCallbackMessage(warnings_.at(0),
+                         "no TTL specified; using SOA MINTTL instead", 1);
+}
+
+TEST_F(MasterLoaderTest, ttlFromPrevious) {
+    // No available default TTL.  2nd and 3rd RR will use the TTL of the
+    // 1st RR.  This will result in a warning, but only for the first time.
+    stringstream zone_stream("a.example.org. 1800 IN A 192.0.2.1\n"
+                             "b.example.org. IN A 192.0.2.2\n"
+                             "c.example.org. IN A 192.0.2.3\n");
+    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+              MasterLoader::DEFAULT);
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(1800));
+    checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(1800));
+    checkRR("c.example.org", RRType::A(), "192.0.2.3", RRTTL(1800));
+
+    EXPECT_EQ(1, warnings_.size());
+    checkCallbackMessage(warnings_.at(0), "using RFC1035 TTL semantics", 2);
+}
+
+TEST_F(MasterLoaderTest, ttlFromPreviousSOA) {
+    // Mixture of the previous two cases: SOA has explicit TTL, followed by
+    // an RR without an explicit TTL.  In this case the minimum TTL won't be
+    // recognized as the "default TTL".
+    stringstream zone_stream("example.org. 100 IN SOA . . 0 0 0 0 1800\n"
+                             "a.example.org. IN A 192.0.2.1\n");
+    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+              MasterLoader::DEFAULT);
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+
+    checkRR("example.org", RRType::SOA(), ". . 0 0 0 0 1800", RRTTL(100));
+    checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(100));
+
+    EXPECT_EQ(1, warnings_.size());
+    checkCallbackMessage(warnings_.at(0), "using RFC1035 TTL semantics", 2);
+}
+
+TEST_F(MasterLoaderTest, ttlUnknown) {
+    // No available TTL is known for the first RR.
+    stringstream zone_stream("a.example.org. IN A 192.0.2.1\n");
+    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+              MasterLoader::DEFAULT);
+    EXPECT_THROW(loader_->load(), MasterLoaderError);
+}
+
+TEST_F(MasterLoaderTest, ttlUnknownAndContinue) {
+    stringstream zone_stream("a.example.org. IN A 192.0.2.1\n"
+                             "b.example.org. 1800 IN A 192.0.2.2\n");
+    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(1800));
+
+    EXPECT_TRUE(warnings_.empty());
+    EXPECT_EQ(1, errors_.size());
+    checkCallbackMessage(errors_.at(0), "no TTL specified; load rejected", 1);
+}
+
+TEST_F(MasterLoaderTest, ttlUnknownAndEOF) {
+    // Similar to the previous case, but the input will be abruptly terminated
+    // after the offending RR.  This will cause an additional warning.
+    stringstream zone_stream("a.example.org. IN A 192.0.2.1");
+    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    EXPECT_TRUE(rrsets_.empty());
+
+    EXPECT_EQ(1, errors_.size());
+    checkCallbackMessage(errors_.at(0), "no TTL specified; load rejected", 1);
+
+    // RDATA implementation can complain about it, too.  To be independent of
+    // its details, we focus on the very last warning.
+    EXPECT_FALSE(warnings_.empty());
+    checkCallbackMessage(*warnings_.rbegin(), "Unexpected end of file", 1);
+}
+
+TEST_F(MasterLoaderTest, ttlOverflow) {
+    stringstream zone_stream;
+    zone_stream << "example.org. IN SOA . . 0 0 0 0 2147483648\n";
+    zone_stream << "$TTL 3600\n"; // reset to an in-range value
+    zone_stream << "$TTL 2147483649\n" << "a.example.org. IN A 192.0.2.1\n";
+    zone_stream << "$TTL 3600\n"; // reset to an in-range value
+    zone_stream << "b.example.org. 2147483650 IN A 192.0.2.2\n";
+    setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+              MasterLoader::DEFAULT);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_EQ(3, rrsets_.size());
+
+    checkRR("example.org", RRType::SOA(), ". . 0 0 0 0 2147483648", RRTTL(0));
+    checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(0));
+    checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(0));
+
+    EXPECT_EQ(4, warnings_.size());
+    checkCallbackMessage(warnings_.at(1),
+                         "TTL 2147483648 > MAXTTL, setting to 0 per RFC2181",
+                         1);
+    checkCallbackMessage(warnings_.at(2),
+                         "TTL 2147483649 > MAXTTL, setting to 0 per RFC2181",
+                         3);
+    checkCallbackMessage(warnings_.at(3),
+                         "TTL 2147483650 > MAXTTL, setting to 0 per RFC2181",
+                         6);
+}
+
 // Test the constructor rejects empty add callback.
 TEST_F(MasterLoaderTest, emptyCallback) {
     EXPECT_THROW(MasterLoader(TEST_DATA_SRCDIR "/example.org",

+ 15 - 4
src/lib/dns/tests/rdata_soa_unittest.cc

@@ -32,12 +32,14 @@ using namespace isc::dns::rdata;
 
 namespace {
 class Rdata_SOA_Test : public RdataTest {
-    // there's nothing to specialize
+protected:
+    Rdata_SOA_Test() : rdata_soa(Name("ns.example.com"),
+                                 Name("root.example.com"),
+                                 2010012601, 3600, 300, 3600000, 1200)
+    {}
+    const generic::SOA rdata_soa;
 };
 
-const generic::SOA rdata_soa(Name("ns.example.com"), Name("root.example.com"),
-                             2010012601, 3600, 300, 3600000, 1200);
-
 TEST_F(Rdata_SOA_Test, createFromText) {
     //TBD
 }
@@ -86,4 +88,13 @@ TEST_F(Rdata_SOA_Test, getSerial) {
     EXPECT_EQ(2010012601, rdata_soa.getSerial().getValue());
 }
 
+TEST_F(Rdata_SOA_Test, getMinimum) {
+    EXPECT_EQ(1200, rdata_soa.getMinimum());
+
+    // Also check with a very large number (with the MSB being 1).
+    EXPECT_EQ(2154848336u, generic::SOA(Name("ns.example.com"),
+                                        Name("root.example.com"),
+                                        0, 0, 0, 0, 0x80706050).getMinimum());
+}
+
 }

+ 17 - 0
src/lib/dns/tests/rrttl_unittest.cc

@@ -20,6 +20,8 @@
 
 #include <dns/tests/unittest_util.h>
 
+#include <boost/scoped_ptr.hpp>
+
 using namespace std;
 using namespace isc;
 using namespace isc::dns;
@@ -85,6 +87,17 @@ TEST_F(RRTTLTest, fromText) {
     EXPECT_THROW(RRTTL("4294967296"), InvalidRRTTL); // must be 32-bit
 }
 
+TEST_F(RRTTLTest, createFromText) {
+    // It returns an actual RRTT iff the given text is recognized as a
+    // valid RR TTL.
+    MaybeRRTTL maybe_ttl = RRTTL::createFromText("3600");
+    EXPECT_TRUE(maybe_ttl);
+    EXPECT_EQ(RRTTL(3600), *maybe_ttl);
+
+    maybe_ttl = RRTTL::createFromText("bad");
+    EXPECT_FALSE(maybe_ttl);
+}
+
 void
 checkUnit(unsigned multiply, char suffix) {
     SCOPED_TRACE(string("Unit check with suffix ") + suffix);
@@ -252,6 +265,10 @@ TEST_F(RRTTLTest, gthan) {
     EXPECT_FALSE(ttl_small > ttl_large);
 }
 
+TEST_F(RRTTLTest, maxTTL) {
+    EXPECT_EQ((1u << 31) - 1, RRTTL::MAX().getValue());
+}
+
 // test operator<<.  We simply confirm it appends the result of toText().
 TEST_F(RRTTLTest, LeftShiftOperator) {
     ostringstream oss;