Commit 66535ab5 authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

[master] Merge branch 'trac2389'

parents e606d8d0 27dcc01e
Differences of Bind 10 to other software
========================================
Bind 9
BIND 9
------
TODO: There are definitely more differences than just this.
......@@ -10,3 +10,11 @@ TODO: There are definitely more differences than just this.
received zone doesn't contain a NS record, bind 9 stops serving the
zone and returns SERVFAIL to queries for that zone. Bind 10 still
uses the previous version of zone.
RDATA implementations:
* IN/A: BIND 10 does not accept abbreviated forms of textual IPv4
addresses for class-IN, type-A RDATA. BIND 9 warns about it but
still accepts it as the standard inet_aton() function. Such forms
should actually be NOT accepted per RFC 1035, but BIND 9 accepts them
probably because of compatibility reasons. Until our strict
(and more correct) behavior causes operations issues, we'll keep it.
......@@ -32,7 +32,7 @@ import sys
#
# Example:
# new_rdata_factory_users = [('a', 'in'), ('a', 'ch'), ('soa', 'generic')]
new_rdata_factory_users = [('aaaa', 'in'),
new_rdata_factory_users = [('a', 'in'), ('aaaa', 'in'),
('cname', 'generic'),
('dname', 'generic'),
('hinfo', 'generic'),
......
......@@ -15,6 +15,8 @@
#include <stdint.h>
#include <string.h>
#include <cerrno>
#include <cstring>
#include <string>
#include <arpa/inet.h> // XXX: for inet_pton/ntop(), not exist in C++ standards
......@@ -23,8 +25,11 @@
#include <exceptions/exceptions.h>
#include <util/buffer.h>
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
#include <dns/master_lexer.h>
#include <dns/master_loader_callbacks.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
......@@ -34,16 +39,91 @@ using namespace isc::util;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
A::A(const std::string& addrstr) {
// RFC1035 states textual representation of IN/A RDATA is
// "four decimal numbers separated by dots without any embedded spaces".
// This is exactly what inet_pton() accepts for AF_INET. In particular,
// it rejects an abbreviated form such as "10.1" meaning "10.0.0.1".
if (inet_pton(AF_INET, addrstr.c_str(), &addr_) != 1) {
namespace {
void
convertToIPv4Addr(const char* src, size_t src_len, uint32_t* dst) {
// This check specifically rejects invalid input that begins with valid
// address text followed by a nul character (and possibly followed by
// further garbage). It cannot be detected by inet_pton().
//
// Note that this is private subroutine of the in::A constructors, which
// pass std::string.size() or StringRegion::len as src_len, so it should
// be equal to strlen() unless there's an intermediate nul character.
if (src_len != strlen(src)) {
isc_throw(InvalidRdataText,
"IN/A RDATA construction from text failed: Address cannot be "
"converted: " << addrstr);
"Bad IN/A RDATA text: unexpected nul in string: '"
<< src << "'");
}
const int result = inet_pton(AF_INET, src, dst);
if (result == 0) {
isc_throw(InvalidRdataText, "Bad IN/A RDATA text: '" << src << "'");
} else if (result < 0) {
isc_throw(isc::Unexpected,
"Unexpected failure in parsing IN/A RDATA text: '"
<< src << "': " << std::strerror(errno));
}
}
}
/// \brief Constructor from string.
///
/// The given string must be a valid textual representation of an IPv4
/// address as specified in RFC1035, that is, four decimal numbers separated
/// by dots without any embedded spaces. Note that it excludes abbreviated
/// forms such as "10.1" to mean "10.0.0.1".
///
/// Internally, this implementation uses the standard inet_pton() library
/// function for the AF_INET family to parse and convert the textual
/// representation. While standard compliant implementations of this function
/// should accept exactly what this constructor expects, specific
/// implementation may behave differently, in which case this constructor
/// will simply accept the result of inet_pton(). In any case, the user of
/// the class shouldn't assume such specific implementation behavior of
/// inet_pton().
///
/// No extra character should be contained in \c addrstr other than the
/// textual address. These include spaces and the nul character.
///
/// \throw InvalidRdata The text extracted by the lexer isn't recognized as
/// a valid IPv4 address.
/// \throw Unexpected Unexpected system error in conversion (this should be
/// very rare).
///
/// \param addrstr Textual representation of IPv4 address to be used as the
/// RDATA.
A::A(const std::string& addrstr) {
convertToIPv4Addr(addrstr.c_str(), addrstr.size(), &addr_);
}
/// \brief Constructor with a context of MasterLexer.
///
/// The \c lexer should point to the beginning of valid textual representation
/// of a class IN A RDATA.
///
/// The acceptable form of the textual address is generally the same as the
/// string version of the constructor, but this version accepts beginning
/// spaces and trailing spaces or other characters. Trailing non space
/// characters would be considered an invalid form in an RR representation,
/// but handling such errors is not the responsibility of this constructor.
/// It also accepts other unusual syntax that would be considered valid
/// in the context of DNS master file; for example, it accepts an IPv4
/// address surrounded by parentheses, such as "(192.0.2.1)", although it's
/// very unlikely to be used for this type of RDATA.
///
/// \throw MasterLexer::LexerError General parsing error such as missing field.
/// \throw InvalidRdata The text extracted by the lexer isn't recognized as
/// a valid IPv4 address.
/// \throw Unexpected Unexpected system error in conversion (this should be
/// very rare).
///
/// \param lexer A \c MasterLexer object parsing a master file for the
/// RDATA to be created
A::A(MasterLexer& lexer, const Name*,
MasterLoader::Options, MasterLoaderCallbacks&)
{
const MasterToken& token = lexer.getNextToken(MasterToken::STRING);
convertToIPv4Addr(token.getStringRegion().beg, token.getStringRegion().len,
&addr_);
}
A::A(InputBuffer& buffer, size_t rdata_len) {
......@@ -61,6 +141,7 @@ A::A(InputBuffer& buffer, size_t rdata_len) {
buffer.readData(&addr_, sizeof(addr_));
}
/// \brief Copy constructor.
A::A(const A& other) : Rdata(), addr_(other.addr_)
{}
......@@ -74,6 +155,7 @@ A::toWire(AbstractMessageRenderer& renderer) const {
renderer.writeData(&addr_, sizeof(addr_));
}
/// \brief Return a textual form of the underlying IPv4 address of the RDATA.
string
A::toText() const {
char addr_string[sizeof("255.255.255.255")];
......@@ -86,6 +168,9 @@ A::toText() const {
return (addr_string);
}
/// \brief Compare two in::A RDATAs.
///
/// In effect, it compares the two RDATA as an unsigned 32-bit integer.
int
A::compare(const Rdata& other) const {
const A& other_a = dynamic_cast<const A&>(other);
......
......@@ -24,6 +24,8 @@
#include <stdint.h>
#include <string.h>
#include <cerrno>
#include <cstring>
#include <string>
#include <arpa/inet.h> // XXX: for inet_pton/ntop(), not exist in C++ standards
......@@ -35,24 +37,72 @@ using namespace isc::util;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
AAAA::AAAA(const std::string& addrstr) {
if (inet_pton(AF_INET6, addrstr.c_str(), &addr_) != 1) {
namespace {
void
convertToIPv6Addr(const char* src, size_t src_len, void* dst) {
// See a_1.cc for this check.
if (src_len != strlen(src)) {
isc_throw(InvalidRdataText,
"IN/AAAA RDATA construction from text failed: "
"Address cannot be converted: " << addrstr);
"Bad IN/AAAA RDATA text: unexpected nul in string: '"
<< src << "'");
}
const int result = inet_pton(AF_INET6, src, dst);
if (result == 0) {
isc_throw(InvalidRdataText, "Bad IN/AAAA RDATA text: '" << src << "'");
} else if (result < 0) {
isc_throw(isc::Unexpected,
"Unexpected failure in parsing IN/AAAA RDATA text: '"
<< src << "': " << std::strerror(errno));
}
}
}
/// \brief Constructor from string.
///
/// The given string must be a valid textual representation of an IPv6
/// address as specified in RFC1886.
///
/// No extra character should be contained in \c addrstr other than the
/// textual address. These include spaces and the nul character.
///
/// \throw InvalidRdata The text extracted by the lexer isn't recognized as
/// a valid IPv6 address.
/// \throw Unexpected Unexpected system error in conversion (this should be
/// very rare).
///
/// \param addrstr Textual representation of IPv6 address to be used as the
/// RDATA.
AAAA::AAAA(const std::string& addrstr) {
convertToIPv6Addr(addrstr.c_str(), addrstr.size(), addr_);
}
/// \brief Constructor with a context of MasterLexer.
///
/// The \c lexer should point to the beginning of valid textual representation
/// of a class IN AAAA RDATA.
///
/// The acceptable form of the textual address is generally the same as the
/// string version of the constructor, but this version is slightly more
/// flexible. See the similar constructor of \c in::A class; the same
/// notes apply here.
///
/// \throw MasterLexer::LexerError General parsing error such as missing field.
/// \throw InvalidRdata The text extracted by the lexer isn't recognized as
/// a valid IPv6 address.
/// \throw Unexpected Unexpected system error in conversion (this should be
/// very rare).
///
/// \param lexer A \c MasterLexer object parsing a master file for the
/// RDATA to be created
AAAA::AAAA(MasterLexer& lexer, const Name*,
MasterLoader::Options, MasterLoaderCallbacks&)
{
const MasterToken& token = lexer.getNextToken(MasterToken::STRING);
if (inet_pton(AF_INET6, token.getStringRegion().beg, &addr_) != 1) {
isc_throw(InvalidRdataText, "Failed to convert '"
<< token.getString() << "' to IN/AAAA RDATA");
}
convertToIPv6Addr(token.getStringRegion().beg, token.getStringRegion().len,
addr_);
}
/// \brief Copy constructor.
AAAA::AAAA(InputBuffer& buffer, size_t rdata_len) {
if (rdata_len != sizeof(addr_)) {
isc_throw(DNSMessageFORMERR,
......@@ -72,6 +122,7 @@ AAAA::AAAA(const AAAA& other) : Rdata() {
memcpy(addr_, other.addr_, sizeof(addr_));
}
/// \brief Return a textual form of the underlying IPv6 address of the RDATA.
void
AAAA::toWire(OutputBuffer& buffer) const {
buffer.writeData(&addr_, sizeof(addr_));
......@@ -86,7 +137,8 @@ string
AAAA::toText() const {
char addr_string[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
if (inet_ntop(AF_INET6, &addr_, addr_string, sizeof(addr_string)) == NULL) {
if (inet_ntop(AF_INET6, &addr_, addr_string, sizeof(addr_string))
== NULL) {
isc_throw(Unexpected,
"Failed to convert IN/AAAA RDATA to textual IPv6 address");
}
......@@ -94,6 +146,9 @@ AAAA::toText() const {
return (string(addr_string));
}
/// \brief Compare two in::AAAA RDATAs.
///
/// In effect, it compares the two RDATA as an unsigned 128-bit integer.
int
AAAA::compare(const Rdata& other) const {
const AAAA& other_a = dynamic_cast<const AAAA&>(other);
......
......@@ -419,16 +419,16 @@ struct ErrorCase {
// Parameter ordering errors
{ "www IN A 3600 192.168.2.7",
"createRdata from text failed: IN/A RDATA construction from text failed",
"createRdata from text failed: Bad IN/A RDATA text: '3600'",
"Incorrect order of class, TTL and type" },
{ "www A IN 3600 192.168.2.8",
"createRdata from text failed: IN/A RDATA construction from text failed",
"createRdata from text failed: Bad IN/A RDATA text: 'IN'",
"Incorrect order of class, TTL and type" },
{ "www 3600 A IN 192.168.2.7",
"createRdata from text failed: IN/A RDATA construction from text failed",
"createRdata from text failed: Bad IN/A RDATA text: 'IN'",
"Incorrect order of class, TTL and type" },
{ "www A 3600 IN 192.168.2.8",
"createRdata from text failed: IN/A RDATA construction from text failed",
"createRdata from text failed: Bad IN/A RDATA text: '3600'",
"Incorrect order of class, TTL and type" },
// Missing type and Rdata
......@@ -440,19 +440,19 @@ struct ErrorCase {
// Missing Rdata
{ "www A",
"createRdata from text failed: IN/A RDATA construction from text failed",
"createRdata from text failed: unexpected end of input",
"Missing Rdata" },
{ "www 3600 A",
"createRdata from text failed: IN/A RDATA construction from text failed",
"createRdata from text failed: unexpected end of input",
"Missing Rdata" },
{ "www IN A",
"createRdata from text failed: IN/A RDATA construction from text failed",
"createRdata from text failed: unexpected end of input",
"Missing Rdata" },
{ "www 3600 IN A",
"createRdata from text failed: IN/A RDATA construction from text failed",
"createRdata from text failed: unexpected end of input",
"Missing Rdata" },
{ "www IN 3600 A",
"createRdata from text failed: IN/A RDATA construction from text failed",
"createRdata from text failed: unexpected end of input",
"Missing Rdata" },
{ "www 3600 IN", NULL, "Unexpected EOLN" },
......
......@@ -12,11 +12,14 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <dns/rdataclass.h>
#include <util/buffer.h>
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
#include <dns/master_lexer.h>
#include <dns/master_loader.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
#include <dns/rrclass.h>
#include <dns/rrtype.h>
......@@ -25,6 +28,8 @@
#include <dns/tests/unittest_util.h>
#include <dns/tests/rdata_unittest.h>
#include <sstream>
using isc::UnitTestUtil;
using namespace std;
using namespace isc::dns;
......@@ -33,20 +38,55 @@ using namespace isc::dns::rdata;
namespace {
class Rdata_IN_A_Test : public RdataTest {
// there's nothing to specialize
protected:
Rdata_IN_A_Test() : rdata_in_a("192.0.2.1") {}
void checkFromTextIN_A(const std::string& rdata_txt,
bool throw_str_version = true,
bool throw_lexer_version = true) {
checkFromText<in::A, InvalidRdataText, InvalidRdataText>(
rdata_txt, rdata_in_a, throw_str_version, throw_lexer_version);
}
const in::A rdata_in_a;
};
const in::A rdata_in_a("192.0.2.1");
const uint8_t wiredata_in_a[] = { 192, 0, 2, 1 };
TEST_F(Rdata_IN_A_Test, createFromText) {
EXPECT_EQ(0, rdata_in_a.compare(in::A("192.0.2.1")));
// Normal case: no exception for either case, so the exception type
// doesn't matter.
checkFromText<in::A, isc::Exception, isc::Exception>("192.0.2.1",
rdata_in_a, false,
false);
// should reject an abbreviated form of IPv4 address
EXPECT_THROW(in::A("10.1"), InvalidRdataText);
checkFromTextIN_A("10.1");
// or an IPv6 address
EXPECT_THROW(in::A("2001:db8::1234"), InvalidRdataText);
checkFromTextIN_A("2001:db8::1234");
// or any meaningless text as an IP address
EXPECT_THROW(in::A("xxx"), InvalidRdataText);
checkFromTextIN_A("xxx");
// trailing white space: only string version throws
checkFromTextIN_A("192.0.2.1 ", true, false);
// same for beginning white space.
checkFromTextIN_A(" 192.0.2.1", true, false);
// same for trailing non-space garbage (note that lexer version still
// ignore it; it's expected to be detected at a higher layer).
checkFromTextIN_A("192.0.2.1 xxx", true, false);
// nul character after a valid textual representation.
string nul_after_addr = "192.0.2.1";
nul_after_addr.push_back(0);
checkFromTextIN_A(nul_after_addr, true, true);
// a valid address surrounded by parentheses; only okay with lexer
checkFromTextIN_A("(192.0.2.1)", true, false);
// input that would cause lexer-specific error; it's bad text as an
// address so should result in the string version, too.
checkFromText<in::A, InvalidRdataText, MasterLexer::LexerError>(
")192.0.2.1", rdata_in_a);
}
TEST_F(Rdata_IN_A_Test, createFromWire) {
......@@ -68,11 +108,6 @@ TEST_F(Rdata_IN_A_Test, createFromWire) {
DNSMessageFORMERR);
}
TEST_F(Rdata_IN_A_Test, createFromLexer) {
EXPECT_EQ(0, rdata_in_a.compare(
*test::createRdataUsingLexer(RRType::A(), RRClass::IN(), "192.0.2.1")));
}
TEST_F(Rdata_IN_A_Test, toWireBuffer) {
rdata_in_a.toWire(obuffer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
......@@ -89,15 +124,17 @@ TEST_F(Rdata_IN_A_Test, toWireRenderer) {
TEST_F(Rdata_IN_A_Test, toText) {
EXPECT_EQ("192.0.2.1", rdata_in_a.toText());
string longaddr("255.255.255.255"); // this shouldn't make the code crash
// this shouldn't make the code crash
const string longaddr("255.255.255.255");
EXPECT_EQ(longaddr, in::A(longaddr).toText());
}
TEST_F(Rdata_IN_A_Test, compare) {
in::A small1("1.1.1.1");
in::A small2("1.2.3.4");
in::A large1("255.255.255.255");
in::A large2("4.3.2.1");
const in::A small1("1.1.1.1");
const in::A small2("1.2.3.4");
const in::A large1("255.255.255.255");
const in::A large2("4.3.2.1");
// trivial case: self equivalence
// cppcheck-suppress uselessCallsCompare
......
......@@ -33,18 +33,61 @@ using namespace isc::dns::rdata;
namespace {
class Rdata_IN_AAAA_Test : public RdataTest {
// there's nothing to specialize
protected:
Rdata_IN_AAAA_Test() : rdata_in_aaaa("2001:db8::1234") {}
// Common check to see the result of in::A Rdata construction either from
// std::string or with MasterLexer object. If it's expected to succeed
// the result should be identical to the commonly used test data
// (rdata_in_a); otherwise it should result in the exception specified as
// the template parameter.
void checkFromTextIN_AAAA(const string& in_aaaa_txt,
bool throw_str_version = true,
bool throw_lexer_version = true)
{
checkFromText<in::AAAA, InvalidRdataText, InvalidRdataText>(
in_aaaa_txt, rdata_in_aaaa, throw_str_version,
throw_lexer_version);
}
const in::AAAA rdata_in_aaaa;
};
const in::AAAA rdata_in_aaaa("2001:db8::1234");
const uint8_t wiredata_in_aaaa[] = {
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x12, 0x34 };
TEST_F(Rdata_IN_AAAA_Test, createFromText) {
rdata_in_aaaa.compare(in::AAAA(string("2001:db8::1234")));
EXPECT_THROW(in::AAAA("192.0.2.1"), InvalidRdataText);
EXPECT_THROW(in::AAAA("xxx"), InvalidRdataText);
// Normal case: no exception for either case, so the exception type
// doesn't matter.
checkFromText<in::AAAA, isc::Exception, isc::Exception>(
"2001:db8::1234", rdata_in_aaaa, false, false);
// should reject an IP4 address.
checkFromTextIN_AAAA("192.0.2.1");
// or any meaningless text as an IPv6 address
checkFromTextIN_AAAA("xxx");
// trailing white space: only string version throws
checkFromTextIN_AAAA("2001:db8::1234 ", true, false);
// same for beginning white space.
checkFromTextIN_AAAA(" 2001:db8::1234", true, false);
// same for trailing non-space garbage (note that lexer version still
// ignore it; it's expected to be detected at a higher layer).
checkFromTextIN_AAAA("2001:db8::1234 xxx", true, false);
// nul character after a valid textual representation.
string nul_after_addr = "2001:db8::1234";
nul_after_addr.push_back(0);
checkFromTextIN_AAAA(nul_after_addr, true, true);
// a valid address surrounded by parentheses; only okay with lexer
checkFromTextIN_AAAA("(2001:db8::1234)", true, false);
// input that would cause lexer-specific error; it's bad text as an
// address so should result in the string version, too.
checkFromText<in::AAAA, InvalidRdataText, MasterLexer::LexerError>(
")2001:db8::1234", rdata_in_aaaa);
}
TEST_F(Rdata_IN_AAAA_Test, createFromWire) {
......
......@@ -39,123 +39,125 @@ protected:
2010012601, 3600, 300, 3600000, 1200)
{}
// Common check to see if the given text can be used to construct SOA
// Rdata that is identical rdata_soa.
void checkFromText(const char* soa_txt, const Name* origin = NULL) {
std::stringstream ss(soa_txt);
MasterLexer lexer;
lexer.pushSource(ss);
if (origin == NULL) {
// from-string constructor works correctly only when origin
// is NULL (by its nature).
EXPECT_EQ(0, generic::SOA(soa_txt).compare(rdata_soa));
}
EXPECT_EQ(0, generic::SOA(lexer, origin, MasterLoader::DEFAULT,
loader_cb).compare(rdata_soa));
}
// Common check if given text (which is invalid as SOA RDATA) is rejected
// with the specified type of exception: ExForString is the expected
// exception for the "from string" constructor; ExForLexer is for the
// constructor with master lexer.
template <typename ExForString, typename ExForLexer>
void checkFromBadTexxt(const char* soa_txt, const Name* origin = NULL) {
EXPECT_THROW(generic::SOA soa(soa_txt), ExForString);
std::stringstream ss(soa_txt);
MasterLexer lexer;
lexer.pushSource(ss);
EXPECT_THROW(generic::SOA soa(lexer, origin, MasterLoader::DEFAULT,
loader_cb), ExForLexer);
void checkFromTextSOA(const string& soa_txt, const Name* origin = NULL,
bool throw_str_version = true,
bool throw_lexer_version = true)
{
checkFromText<generic::SOA, ExForString, ExForLexer>(
soa_txt, rdata_soa, throw_str_version, throw_lexer_version,
origin);
}
const generic::SOA rdata_soa;
};
TEST_F(Rdata_SOA_Test, createFromText) {
// Below we specify isc::Exception as a dummy value for the exception type
// in case it's not expected to throw an exception; the type isn't used
// in the check code.
// A simple case.
checkFromText("ns.example.com. root.example.com. "
"2010012601 3600 300 3600000 1200");
checkFromTextSOA<isc::Exception, isc::Exception>(
"ns.example.com. root.example.com. 2010012601 3600 300 3600000 1200",
NULL, false, false);
// Beginning and trailing space are ignored.
checkFromText(" ns.example.com. root.example.com. "
"2010012601 3600 300 3600000 1200 ");
checkFromTextSOA<isc::Exception, isc::Exception>(
" ns.example.com. root.example.com. "
"2010012601 3600 300 3600000 1200 ", NULL, false, false);
// using extended TTL-like form for some parameters.
checkFromText("ns.example.com. root.example.com. "
"2010012601 1H 5M 1000H 20M");
checkFromTextSOA<isc::Exception, isc::Exception>(
"ns.example.com. root.example.com. 2010012601 1H 5M 1000H 20M",
NULL, false, false);
// multi-line.
checkFromText("ns.example.com. (root.example.com.\n"
"2010012601 1H 5M 1000H) 20M");
checkFromTextSOA<isc::Exception, isc::Exception>(
"ns.example.com. (root.example.com.\n"
"2010012601 1H 5M 1000H) 20M", NULL, false, false);
// relative names for MNAME and RNAME with a separate origin (lexer
// version only)
const Name origin("example.com");
checkFromText("ns root 2010012601 1H 5M 1000H 20M", &origin);
checkFromTextSOA<MissingNameOrigin, isc::Exception>(
"ns root 2010012601 1H 5M 1000H 20M", &origin, true, false);
// with the '@' notation with a separate origin (lexer version only)
// with the '@' notation with a separate origin (lexer version only;
// string version would throw)
const Name full_mname("ns.example.com");
checkFromText("@ root.example.com. 2010012601 1H 5M 1000H 20M",
&full_mname);