BIND 10 master, updated. 4c178871eba133fbbf56c2dac1dde1a28ad20477 [master] removed garbage line (that had to be removed in the previous change)
BIND 10 source code commits
bind10-changes at lists.isc.org
Thu May 5 17:59:01 UTC 2011
The branch, master has been updated
via 4c178871eba133fbbf56c2dac1dde1a28ad20477 (commit)
via ffa2f0672084c1f16e5784cdcdd55822f119feaa (commit)
via 25bc145bb597683411ece6ad7cb72fc0fabedeae (commit)
via 4a2c0e7a8852496e9b8f9a56f5058cb1f55d3f55 (commit)
via 25301e86fb8aa4cb8d98933a0c3b7dcf46f8c4be (commit)
via f1dfe74d6b9b2b8c4dc85dd39a43084dc2b73aa9 (commit)
via 6daf339b7a83843a12d4938424063d1b96929924 (commit)
via 3ce5e0a2b96c84ae003ea372bb84ebb19ca355e8 (commit)
via 9e8b5ec4233217fba5c7e9b17afd6c01e4fc7b79 (commit)
via cca71e70d0667d066ba5ab63c65b21d83cbb896c (commit)
via 2d514a764119f29d3e2fbb04366d880df1866928 (commit)
via e22700fc2021180a0dab3f312df5fdabe9bc47e2 (commit)
via 98d147ea68fc1e62b6e7e3bc74c80e9457b5249f (commit)
via 0e3b55edc75e91ea11df51ecffae0ae4ff07fdac (commit)
via 0261bbec3a5cd8770aee567aaebab2a4f93c2303 (commit)
via e997ff18a6bf1eeb23391e0957db0defdecd0213 (commit)
via 8320ec6eb230fe761ae4769367f10383e967655c (commit)
from 9a2ca8e5de8a23018ebb0ac7fee819c065542ff1 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 4c178871eba133fbbf56c2dac1dde1a28ad20477
Author: JINMEI Tatuya <jinmei at isc.org>
Date: Thu May 5 10:58:34 2011 -0700
[master] removed garbage line (that had to be removed in the previous change)
commit ffa2f0672084c1f16e5784cdcdd55822f119feaa
Merge: 9a2ca8e 25bc145
Author: JINMEI Tatuya <jinmei at isc.org>
Date: Thu May 5 10:44:33 2011 -0700
[master] Merge branch 'trac813' with fixing conflicts
Conflicts:
src/lib/dns/tests/tsigrecord_unittest.cc
-----------------------------------------------------------------------
Summary of changes:
src/lib/dns/message.cc | 143 +++++++++++++++-----
src/lib/dns/message.h | 30 ++++
src/lib/dns/python/tests/message_python_test.py | 2 +-
src/lib/dns/tests/message_unittest.cc | 111 +++++++++++++++
src/lib/dns/tests/run_unittests.cc | 3 +
src/lib/dns/tests/testdata/Makefile.am | 12 ++-
src/lib/dns/tests/testdata/gen-wiredata.py.in | 105 +++++++++++---
...essage_toWire2.spec => message_fromWire12.spec} | 6 +-
...essage_toWire2.spec => message_fromWire13.spec} | 7 +-
...essage_toWire2.spec => message_fromWire14.spec} | 6 +-
...essage_toWire2.spec => message_fromWire15.spec} | 9 +-
...essage_toWire2.spec => message_fromWire16.spec} | 4 +-
src/lib/dns/tests/testdata/message_toText1.spec | 24 ++++
src/lib/dns/tests/testdata/message_toText1.txt | 14 ++
src/lib/dns/tests/testdata/message_toText2.spec | 14 ++
src/lib/dns/tests/testdata/message_toText2.txt | 8 +
src/lib/dns/tests/testdata/message_toText3.spec | 31 +++++
src/lib/dns/tests/testdata/message_toText3.txt | 17 +++
src/lib/dns/tests/tsigrecord_unittest.cc | 48 ++++++-
src/lib/dns/tsigrecord.cc | 36 +++++
src/lib/dns/tsigrecord.h | 100 +++++++++++++-
src/lib/util/unittests/Makefile.am | 2 +
src/lib/util/unittests/{newhook.cc => testdata.cc} | 60 +++++----
src/lib/util/unittests/{newhook.cc => testdata.h} | 53 ++++----
src/lib/util/unittests/textdata.h | 103 ++++++++++++++
25 files changed, 811 insertions(+), 137 deletions(-)
copy src/lib/dns/tests/testdata/{message_toWire2.spec => message_fromWire12.spec} (69%)
copy src/lib/dns/tests/testdata/{message_toWire2.spec => message_fromWire13.spec} (65%)
copy src/lib/dns/tests/testdata/{message_toWire2.spec => message_fromWire14.spec} (74%)
copy src/lib/dns/tests/testdata/{message_toWire2.spec => message_fromWire15.spec} (65%)
copy src/lib/dns/tests/testdata/{message_toWire2.spec => message_fromWire16.spec} (77%)
create mode 100644 src/lib/dns/tests/testdata/message_toText1.spec
create mode 100644 src/lib/dns/tests/testdata/message_toText1.txt
create mode 100644 src/lib/dns/tests/testdata/message_toText2.spec
create mode 100644 src/lib/dns/tests/testdata/message_toText2.txt
create mode 100644 src/lib/dns/tests/testdata/message_toText3.spec
create mode 100644 src/lib/dns/tests/testdata/message_toText3.txt
copy src/lib/util/unittests/{newhook.cc => testdata.cc} (51%)
copy src/lib/util/unittests/{newhook.cc => testdata.h} (51%)
create mode 100644 src/lib/util/unittests/textdata.h
-----------------------------------------------------------------------
diff --git a/src/lib/dns/message.cc b/src/lib/dns/message.cc
index 770698a..bf7ccd5 100644
--- a/src/lib/dns/message.cc
+++ b/src/lib/dns/message.cc
@@ -83,7 +83,7 @@ const unsigned int HEADERFLAG_MASK = 0x87b0;
const uint16_t MESSAGE_REPLYPRESERVE = (Message::HEADERFLAG_RD |
Message::HEADERFLAG_CD);
-const char *sectiontext[] = {
+const char* const sectiontext[] = {
"QUESTION",
"ANSWER",
"AUTHORITY",
@@ -116,8 +116,8 @@ public:
vector<QuestionPtr> questions_;
vector<RRsetPtr> rrsets_[NUM_SECTIONS];
ConstEDNSPtr edns_;
+ ConstTSIGRecordPtr tsig_rr_;
- // tsig/sig0: TODO
// RRsetsSorter* sorter_; : TODO
void init();
@@ -125,6 +125,16 @@ public:
void setRcode(const Rcode& rcode);
int parseQuestion(InputBuffer& buffer);
int parseSection(const Message::Section section, InputBuffer& buffer);
+ void addRR(Message::Section section, const Name& name,
+ const RRClass& rrclass, const RRType& rrtype,
+ const RRTTL& ttl, ConstRdataPtr rdata);
+ void addEDNS(Message::Section section, const Name& name,
+ const RRClass& rrclass, const RRType& rrtype,
+ const RRTTL& ttl, const Rdata& rdata);
+ void addTSIG(Message::Section section, unsigned int count,
+ const InputBuffer& buffer, size_t start_position,
+ const Name& name, const RRClass& rrclass,
+ const RRTTL& ttl, const Rdata& rdata);
void toWire(AbstractMessageRenderer& renderer, TSIGContext* tsig_ctx);
};
@@ -143,6 +153,7 @@ MessageImpl::init() {
rcode_ = NULL;
opcode_ = NULL;
edns_ = EDNSPtr();
+ tsig_rr_ = ConstTSIGRecordPtr();
for (int i = 0; i < NUM_SECTIONS; ++i) {
counts_[i] = 0;
@@ -413,6 +424,16 @@ Message::setEDNS(ConstEDNSPtr edns) {
impl_->edns_ = edns;
}
+const TSIGRecord*
+Message::getTSIGRecord() const {
+ if (impl_->mode_ != Message::PARSE) {
+ isc_throw(InvalidMessageOperation,
+ "getTSIGRecord performed in non-parse mode");
+ }
+
+ return (impl_->tsig_rr_.get());
+}
+
unsigned int
Message::getRRCount(const Section section) const {
if (section >= MessageImpl::NUM_SECTIONS) {
@@ -649,6 +670,9 @@ MessageImpl::parseSection(const Message::Section section,
unsigned int added = 0;
for (unsigned int count = 0; count < counts_[section]; ++count) {
+ // We need to remember the start position for TSIG processing
+ const size_t start_position = buffer.getPosition();
+
const Name name(buffer);
// buffer must store at least RR TYPE, RR CLASS, TTL, and RDLEN.
@@ -666,32 +690,12 @@ MessageImpl::parseSection(const Message::Section section,
ConstRdataPtr rdata = createRdata(rrtype, rrclass, buffer, rdlen);
if (rrtype == RRType::OPT()) {
- if (section != Message::SECTION_ADDITIONAL) {
- isc_throw(DNSMessageFORMERR,
- "EDNS OPT RR found in an invalid section");
- }
- if (edns_) {
- isc_throw(DNSMessageFORMERR, "multiple EDNS OPT RR found");
- }
-
- uint8_t extended_rcode;
- edns_ = ConstEDNSPtr(createEDNSFromRR(name, rrclass, rrtype, ttl,
- *rdata, extended_rcode));
- setRcode(Rcode(rcode_->getCode(), extended_rcode));
- continue;
+ addEDNS(section, name, rrclass, rrtype, ttl, *rdata);
+ } else if (rrtype == RRType::TSIG()) {
+ addTSIG(section, count, buffer, start_position, name, rrclass, ttl,
+ *rdata);
} else {
- vector<RRsetPtr>::iterator it =
- find_if(rrsets_[section].begin(), rrsets_[section].end(),
- MatchRR(name, rrtype, rrclass));
- if (it != rrsets_[section].end()) {
- (*it)->setTTL(min((*it)->getTTL(), ttl));
- (*it)->addRdata(rdata);
- } else {
- RRsetPtr rrset =
- RRsetPtr(new RRset(name, rrclass, rrtype, ttl));
- rrset->addRdata(rdata);
- rrsets_[section].push_back(rrset);
- }
+ addRR(section, name, rrclass, rrtype, ttl, rdata);
++added;
}
}
@@ -699,6 +703,65 @@ MessageImpl::parseSection(const Message::Section section,
return (added);
}
+void
+MessageImpl::addRR(Message::Section section, const Name& name,
+ const RRClass& rrclass, const RRType& rrtype,
+ const RRTTL& ttl, ConstRdataPtr rdata)
+{
+ vector<RRsetPtr>::iterator it =
+ find_if(rrsets_[section].begin(), rrsets_[section].end(),
+ MatchRR(name, rrtype, rrclass));
+ if (it != rrsets_[section].end()) {
+ (*it)->setTTL(min((*it)->getTTL(), ttl));
+ (*it)->addRdata(rdata);
+ } else {
+ RRsetPtr rrset(new RRset(name, rrclass, rrtype, ttl));
+ rrset->addRdata(rdata);
+ rrsets_[section].push_back(rrset);
+ }
+}
+
+void
+MessageImpl::addEDNS(Message::Section section, const Name& name,
+ const RRClass& rrclass, const RRType& rrtype,
+ const RRTTL& ttl, const Rdata& rdata)
+{
+ if (section != Message::SECTION_ADDITIONAL) {
+ isc_throw(DNSMessageFORMERR,
+ "EDNS OPT RR found in an invalid section");
+ }
+ if (edns_) {
+ isc_throw(DNSMessageFORMERR, "multiple EDNS OPT RR found");
+ }
+
+ uint8_t extended_rcode;
+ edns_ = ConstEDNSPtr(createEDNSFromRR(name, rrclass, rrtype, ttl, rdata,
+ extended_rcode));
+ setRcode(Rcode(rcode_->getCode(), extended_rcode));
+}
+
+void
+MessageImpl::addTSIG(Message::Section section, unsigned int count,
+ const InputBuffer& buffer, size_t start_position,
+ const Name& name, const RRClass& rrclass,
+ const RRTTL& ttl, const Rdata& rdata)
+{
+ if (section != Message::SECTION_ADDITIONAL) {
+ isc_throw(DNSMessageFORMERR,
+ "TSIG RR found in an invalid section");
+ }
+ if (count != counts_[section] - 1) {
+ isc_throw(DNSMessageFORMERR, "TSIG RR is not the last record");
+ }
+ if (tsig_rr_) {
+ isc_throw(DNSMessageFORMERR, "multiple TSIG RRs found");
+ }
+ tsig_rr_ = ConstTSIGRecordPtr(new TSIGRecord(name, rrclass,
+ ttl, rdata,
+ buffer.getPosition() -
+ start_position));
+}
+
namespace {
template <typename T>
struct SectionFormatter {
@@ -732,31 +795,31 @@ Message::toText() const {
// for simplicity we don't consider extended rcode (unlike BIND9)
s += ", status: " + impl_->rcode_->toText();
s += ", id: " + boost::lexical_cast<string>(impl_->qid_);
- s += "\n;; flags: ";
+ s += "\n;; flags:";
if (getHeaderFlag(HEADERFLAG_QR)) {
- s += "qr ";
+ s += " qr";
}
if (getHeaderFlag(HEADERFLAG_AA)) {
- s += "aa ";
+ s += " aa";
}
if (getHeaderFlag(HEADERFLAG_TC)) {
- s += "tc ";
+ s += " tc";
}
if (getHeaderFlag(HEADERFLAG_RD)) {
- s += "rd ";
+ s += " rd";
}
if (getHeaderFlag(HEADERFLAG_RA)) {
- s += "ra ";
+ s += " ra";
}
if (getHeaderFlag(HEADERFLAG_AD)) {
- s += "ad ";
+ s += " ad";
}
if (getHeaderFlag(HEADERFLAG_CD)) {
- s += "cd ";
+ s += " cd";
}
// for simplicity, don't consider the update case for now
- s += "; QUESTION: " +
+ s += "; QUERY: " + // note: not "QUESTION" to be compatible with BIND 9 dig
lexical_cast<string>(impl_->counts_[SECTION_QUESTION]);
s += ", ANSWER: " +
lexical_cast<string>(impl_->counts_[SECTION_ANSWER]);
@@ -767,6 +830,9 @@ Message::toText() const {
if (impl_->edns_ != NULL) {
++arcount;
}
+ if (impl_->tsig_rr_ != NULL) {
+ ++arcount;
+ }
s += ", ADDITIONAL: " + lexical_cast<string>(arcount) + "\n";
if (impl_->edns_ != NULL) {
@@ -803,6 +869,11 @@ Message::toText() const {
SectionFormatter<RRsetPtr>(SECTION_ADDITIONAL, s));
}
+ if (impl_->tsig_rr_ != NULL) {
+ s += "\n;; TSIG PSEUDOSECTION:\n";
+ s += impl_->tsig_rr_->toText();
+ }
+
return (s);
}
diff --git a/src/lib/dns/message.h b/src/lib/dns/message.h
index cd3ae88..8a657da 100644
--- a/src/lib/dns/message.h
+++ b/src/lib/dns/message.h
@@ -34,6 +34,7 @@ class InputBuffer;
namespace dns {
class TSIGContext;
+class TSIGRecord;
///
/// \brief A standard DNS module exception that is thrown if a wire format
@@ -369,6 +370,25 @@ public:
/// \c Message.
void setEDNS(ConstEDNSPtr edns);
+ /// \brief Return, if any, the TSIG record contained in the received
+ /// message.
+ ///
+ /// Currently, this method is only intended to return a TSIG record
+ /// for an incoming message built via the \c fromWire() method in the
+ /// PARSE mode. A call to this method in the RENDER mode is invalid and
+ /// result in an exception. Also, calling this method is meaningless
+ /// unless \c fromWire() is performed.
+ ///
+ /// The returned pointer is valid only during the lifetime of the
+ /// \c Message object and until \c clear() is called. The \c Message
+ /// object retains the ownership of \c TSIGRecord; the caller must not
+ /// try to delete it.
+ ///
+ /// \exception InvalidMessageOperation Message is not in the PARSE mode.
+ ///
+ /// \return A pointer to the stored \c TSIGRecord or \c NULL.
+ const TSIGRecord* getTSIGRecord() const;
+
/// \brief Returns the number of RRs contained in the given section.
///
/// In the \c PARSE mode, the returned value may not be identical to
@@ -582,6 +602,16 @@ private:
/// that originated the asynchronous call falls out of scope.
typedef boost::shared_ptr<Message> MessagePtr;
+/// Insert the \c Message as a string into stream.
+///
+/// This method convert \c message into a string and inserts it into the
+/// output stream \c os.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param record A \c Message object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
std::ostream& operator<<(std::ostream& os, const Message& message);
}
}
diff --git a/src/lib/dns/python/tests/message_python_test.py b/src/lib/dns/python/tests/message_python_test.py
index 811ecf3..72807cc 100644
--- a/src/lib/dns/python/tests/message_python_test.py
+++ b/src/lib/dns/python/tests/message_python_test.py
@@ -317,7 +317,7 @@ class MessageTest(unittest.TestCase):
msg_str =\
""";; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4149
-;; flags: qr aa rd ; QUESTION: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
+;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;test.example.com. IN A
diff --git a/src/lib/dns/tests/message_unittest.cc b/src/lib/dns/tests/message_unittest.cc
index c92b950..700e2bd 100644
--- a/src/lib/dns/tests/message_unittest.cc
+++ b/src/lib/dns/tests/message_unittest.cc
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <fstream>
+
#include <boost/scoped_ptr.hpp>
#include <exceptions/exceptions.h>
@@ -19,6 +21,9 @@
#include <util/buffer.h>
#include <util/time_utilities.h>
+#include <util/unittests/testdata.h>
+#include <util/unittests/textdata.h>
+
#include <dns/edns.h>
#include <dns/exceptions.h>
#include <dns/message.h>
@@ -67,6 +72,10 @@ extern int64_t (*gettimeFunction)();
}
}
+// XXX: this is defined as class static constants, but some compilers
+// seemingly cannot find the symbol when used in the EXPECT_xxx macros.
+const uint16_t TSIGContext::DEFAULT_FUDGE;
+
namespace {
class MessageTest : public ::testing::Test {
protected:
@@ -185,6 +194,70 @@ TEST_F(MessageTest, setEDNS) {
EXPECT_EQ(edns, message_render.getEDNS());
}
+TEST_F(MessageTest, fromWireWithTSIG) {
+ // Initially there should be no TSIG
+ EXPECT_EQ(static_cast<void*>(NULL), message_parse.getTSIGRecord());
+
+ // getTSIGRecord() is only valid in the parse mode.
+ EXPECT_THROW(message_render.getTSIGRecord(), InvalidMessageOperation);
+
+ factoryFromFile(message_parse, "message_toWire2.wire");
+ const char expected_mac[] = {
+ 0x22, 0x70, 0x26, 0xad, 0x29, 0x7b, 0xee, 0xe7,
+ 0x21, 0xce, 0x6c, 0x6f, 0xff, 0x1e, 0x9e, 0xf3
+ };
+ const TSIGRecord* tsig_rr = message_parse.getTSIGRecord();
+ ASSERT_NE(static_cast<void*>(NULL), tsig_rr);
+ EXPECT_EQ(Name("www.example.com"), tsig_rr->getName());
+ EXPECT_EQ(85, tsig_rr->getLength()); // see TSIGRecordTest.getLength
+ EXPECT_EQ(TSIGKey::HMACMD5_NAME(), tsig_rr->getRdata().getAlgorithm());
+ EXPECT_EQ(0x4da8877a, tsig_rr->getRdata().getTimeSigned());
+ EXPECT_EQ(TSIGContext::DEFAULT_FUDGE, tsig_rr->getRdata().getFudge());
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ tsig_rr->getRdata().getMAC(),
+ tsig_rr->getRdata().getMACSize(),
+ expected_mac, sizeof(expected_mac));
+ EXPECT_EQ(0, tsig_rr->getRdata().getError());
+ EXPECT_EQ(0, tsig_rr->getRdata().getOtherLen());
+ EXPECT_EQ(static_cast<void*>(NULL), tsig_rr->getRdata().getOtherData());
+
+ // If we clear the message for reuse, the recorded TSIG will be cleared.
+ message_parse.clear(Message::PARSE);
+ EXPECT_EQ(static_cast<void*>(NULL), message_parse.getTSIGRecord());
+}
+
+TEST_F(MessageTest, fromWireWithTSIGCompressed) {
+ // Mostly same as fromWireWithTSIG, but the TSIG owner name is compressed.
+ factoryFromFile(message_parse, "message_fromWire12.wire");
+ const TSIGRecord* tsig_rr = message_parse.getTSIGRecord();
+ ASSERT_NE(static_cast<void*>(NULL), tsig_rr);
+ EXPECT_EQ(Name("www.example.com"), tsig_rr->getName());
+ // len(www.example.com) = 17, but when fully compressed, the length is
+ // 2 bytes. So the length of the record should be 15 bytes shorter.
+ EXPECT_EQ(70, tsig_rr->getLength());
+}
+
+TEST_F(MessageTest, fromWireWithBadTSIG) {
+ // Multiple TSIG RRs
+ EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire13.wire"),
+ DNSMessageFORMERR);
+ message_parse.clear(Message::PARSE);
+
+ // TSIG in the answer section (must be in additional)
+ EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire14.wire"),
+ DNSMessageFORMERR);
+ message_parse.clear(Message::PARSE);
+
+ // TSIG is not the last record.
+ EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire15.wire"),
+ DNSMessageFORMERR);
+ message_parse.clear(Message::PARSE);
+
+ // Unexpected RR Class (this will fail in constructing TSIGRecord)
+ EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire16.wire"),
+ DNSMessageFORMERR);
+}
+
TEST_F(MessageTest, getRRCount) {
// by default all counters should be 0
EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_QUESTION));
@@ -607,6 +680,44 @@ TEST_F(MessageTest, toWireWithoutRcode) {
EXPECT_THROW(message_render.toWire(renderer), InvalidMessageOperation);
}
+TEST_F(MessageTest, toText) {
+ // Check toText() output for a typical DNS response with records in
+ // all sections
+ ifstream ifs;
+ unittests::openTestData("message_toText1.txt", ifs);
+ factoryFromFile(message_parse, "message_toText1.wire");
+ {
+ SCOPED_TRACE("Message toText test (basic case)");
+ unittests::matchTextData(ifs, message_parse.toText());
+ }
+
+ // Another example with EDNS. The expected data was slightly modified
+ // from the dig output (other than replacing tabs with a space): adding
+ // a newline after the "OPT PSEUDOSECTION". This is an intentional change
+ // in our version for better readability.
+ ifs.close();
+ message_parse.clear(Message::PARSE);
+ unittests::openTestData("message_toText2.txt", ifs);
+ factoryFromFile(message_parse, "message_toText2.wire");
+ {
+ SCOPED_TRACE("Message toText test with EDNS");
+ unittests::matchTextData(ifs, message_parse.toText());
+ }
+
+ // Another example with TSIG. The expected data was slightly modified
+ // from the dig output (other than replacing tabs with a space): removing
+ // a redundant white space at the end of TSIG RDATA. We'd rather consider
+ // it a dig's defect than a feature.
+ ifs.close();
+ message_parse.clear(Message::PARSE);
+ unittests::openTestData("message_toText3.txt", ifs);
+ factoryFromFile(message_parse, "message_toText3.wire");
+ {
+ SCOPED_TRACE("Message toText test with TSIG");
+ unittests::matchTextData(ifs, message_parse.toText());
+ }
+}
+
TEST_F(MessageTest, toTextWithoutOpcode) {
message_render.setRcode(Rcode::NOERROR());
EXPECT_THROW(message_render.toText(), InvalidMessageOperation);
diff --git a/src/lib/dns/tests/run_unittests.cc b/src/lib/dns/tests/run_unittests.cc
index 3cdc61d..18eb0a5 100644
--- a/src/lib/dns/tests/run_unittests.cc
+++ b/src/lib/dns/tests/run_unittests.cc
@@ -14,13 +14,16 @@
#include <gtest/gtest.h>
+#include <util/unittests/testdata.h>
#include <dns/tests/unittest_util.h>
int
main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
isc::UnitTestUtil::addDataPath(TEST_DATA_SRCDIR);
+ isc::util::unittests::addTestDataPath(TEST_DATA_SRCDIR);
isc::UnitTestUtil::addDataPath(TEST_DATA_BUILDDIR);
+ isc::util::unittests::addTestDataPath(TEST_DATA_BUILDDIR);
return (RUN_ALL_TESTS());
}
diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am
index 0c26393..8a83aae 100644
--- a/src/lib/dns/tests/testdata/Makefile.am
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -3,7 +3,12 @@ CLEANFILES = *.wire
BUILT_SOURCES = edns_toWire1.wire edns_toWire2.wire edns_toWire3.wire
BUILT_SOURCES += edns_toWire4.wire
BUILT_SOURCES += message_fromWire10.wire message_fromWire11.wire
+BUILT_SOURCES += message_fromWire12.wire message_fromWire13.wire
+BUILT_SOURCES += message_fromWire14.wire message_fromWire15.wire
+BUILT_SOURCES += message_fromWire16.wire
BUILT_SOURCES += message_toWire2.wire message_toWire3.wire
+BUILT_SOURCES += message_toText1.wire message_toText2.wire
+BUILT_SOURCES += message_toText3.wire
BUILT_SOURCES += name_toWire5.wire name_toWire6.wire
BUILT_SOURCES += rdatafields1.wire rdatafields2.wire rdatafields3.wire
BUILT_SOURCES += rdatafields4.wire rdatafields5.wire rdatafields6.wire
@@ -47,8 +52,13 @@ EXTRA_DIST += message_fromWire3 message_fromWire4
EXTRA_DIST += message_fromWire5 message_fromWire6
EXTRA_DIST += message_fromWire7 message_fromWire8
EXTRA_DIST += message_fromWire9 message_fromWire10.spec
-EXTRA_DIST += message_fromWire11.spec
+EXTRA_DIST += message_fromWire11.spec message_fromWire12.spec
+EXTRA_DIST += message_fromWire13.spec message_fromWire14.spec
+EXTRA_DIST += message_fromWire15.spec message_fromWire16.spec
EXTRA_DIST += message_toWire1 message_toWire2.spec message_toWire3.spec
+EXTRA_DIST += message_toText1.txt message_toText1.spec
+EXTRA_DIST += message_toText2.txt message_toText2.spec
+EXTRA_DIST += message_toText3.txt message_toText3.spec
EXTRA_DIST += name_fromWire1 name_fromWire2 name_fromWire3_1 name_fromWire3_2
EXTRA_DIST += name_fromWire4 name_fromWire6 name_fromWire7 name_fromWire8
EXTRA_DIST += name_fromWire9 name_fromWire10 name_fromWire11 name_fromWire12
diff --git a/src/lib/dns/tests/testdata/gen-wiredata.py.in b/src/lib/dns/tests/testdata/gen-wiredata.py.in
index 15a2b12..7a82bfd 100755
--- a/src/lib/dns/tests/testdata/gen-wiredata.py.in
+++ b/src/lib/dns/tests/testdata/gen-wiredata.py.in
@@ -15,7 +15,7 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-import configparser, re, time, sys
+import configparser, re, time, socket, sys
from datetime import datetime
from optparse import OptionParser
@@ -215,6 +215,74 @@ class EDNS:
f.write('# RDLEN=%d\n' % self.rdlen)
f.write('%04x\n' % self.rdlen)
+class RR:
+ '''This is a base class for various types of RR test data.
+ For each RR type (A, AAAA, NS, etc), we define a derived class of RR
+ to dump type specific RDATA parameters. This class defines parameters
+ common to all types of RDATA, namely the owner name, RR class and TTL.
+ The dump() method of derived classes are expected to call dump_header(),
+ whose default implementation is provided in this class. This method
+ decides whether to dump the test data as an RR (with name, type, class)
+ or only as RDATA (with its length), and dumps the corresponding data
+ via the specified file object.
+
+ By convention we assume derived classes are named after the common
+ standard mnemonic of the corresponding RR types. For example, the
+ derived class for the RR type SOA should be named "SOA".
+
+ Configurable parameters are as follows:
+ - as_rr (bool): Whether or not the data is to be dumped as an RR. False
+ by default.
+ - rr_class (string): The RR class of the data. Only meaningful when the
+ data is dumped as an RR. Default is 'IN'.
+ - rr_ttl (integer): The TTL value of the RR. Only meaningful when the
+ data is dumped as an RR. Default is 86400 (1 day).
+ '''
+
+ def __init__(self):
+ self.as_rr = False
+ # only when as_rr is True, same for class/TTL:
+ self.rr_name = 'example.com'
+ self.rr_class = 'IN'
+ self.rr_ttl = 86400
+ def dump_header(self, f, rdlen):
+ type_txt = self.__class__.__name__
+ type_code = parse_value(type_txt, dict_rrtype)
+ if self.as_rr:
+ rrclass = parse_value(self.rr_class, dict_rrclass)
+ f.write('\n# %s RR (QNAME=%s Class=%s TTL=%d RDLEN=%d)\n' %
+ (type_txt, self.rr_name,
+ code_totext(rrclass, rdict_rrclass), self.rr_ttl, rdlen))
+ f.write('%s %04x %04x %08x %04x\n' %
+ (encode_name(self.rr_name), type_code, rrclass,
+ self.rr_ttl, rdlen))
+ else:
+ f.write('\n# %s RDATA (RDLEN=%d)\n' % (type_txt, rdlen))
+ f.write('%04x\n' % rdlen)
+
+class A(RR):
+ rdlen = 4 # fixed by default
+ address = '192.0.2.1'
+
+ def dump(self, f):
+ self.dump_header(f, self.rdlen)
+ f.write('# Address=%s\n' % (self.address))
+ bin_address = socket.inet_aton(self.address)
+ f.write('%02x%02x%02x%02x\n' % (bin_address[0], bin_address[1],
+ bin_address[2], bin_address[3]))
+
+class NS(RR):
+ rdlen = None # auto calculate
+ nsname = 'ns.example.com'
+
+ def dump(self, f):
+ nsname_wire = encode_name(self.nsname)
+ if self.rdlen is None:
+ self.rdlen = len(nsname_wire) / 2
+ self.dump_header(f, self.rdlen)
+ f.write('# NS name=%s\n' % (self.nsname))
+ f.write('%s\n' % nsname_wire)
+
class SOA:
# this currently doesn't support name compression within the RDATA.
rdlen = -1 # auto-calculate
@@ -432,12 +500,7 @@ class RRSIG:
f.write('# Tag=%d Signer=%s and Signature\n' % (self.tag, self.signer))
f.write('%04x %s %s\n' % (self.tag, name_wire, sig_wire))
-class TSIG:
- as_rr = False
- rr_name = 'example.com' # only when as_rr is True, same for class/TTL
- rr_class = parse_value('ANY', dict_rrclass)
- rr_ttl = 0
-
+class TSIG(RR):
rdlen = None # auto-calculate
algorithm = 'hmac-sha256'
time_signed = 1286978795 # arbitrarily chosen default
@@ -449,12 +512,18 @@ class TSIG:
other_len = None # 6 if error is BADTIME; otherwise 0
other_data = None # use time_signed + fudge + 1 for BADTIME
dict_macsize = { 'hmac-md5' : 16, 'hmac-sha1' : 20, 'hmac-sha256' : 32 }
+
+ # TSIG has some special defaults
+ def __init__(self):
+ super().__init__()
+ self.rr_class = 'ANY'
+ self.rr_ttl = 0
+
def dump(self, f):
if str(self.algorithm) == 'hmac-md5':
name_wire = encode_name('hmac-md5.sig-alg.reg.int')
else:
name_wire = encode_name(self.algorithm)
- rdlen = self.rdlen
mac_size = self.mac_size
if mac_size is None:
if self.algorithm in self.dict_macsize.keys():
@@ -473,19 +542,10 @@ class TSIG:
if self.error == 18 else ''
else:
other_data = encode_string(self.other_data, other_len)
- if rdlen is None:
- rdlen = int(len(name_wire) / 2 + 16 + len(mac) / 2 + \
- len(other_data) / 2)
- if self.as_rr:
- f.write('\n# TSIG RR (QNAME=%s Class=%s TTL=%d RDLEN=%d)\n' %
- (self.rr_name, rdict_rrclass[self.rr_class],
- self.rr_ttl, rdlen))
- f.write('%s %04x %04x %08x %04x\n' %
- (encode_name(self.rr_name), dict_rrtype['tsig'],
- self.rr_class, self.rr_ttl, rdlen))
- else:
- f.write('\n# TSIG RDATA (RDLEN=%d)\n' % rdlen)
- f.write('%04x\n' % rdlen);
+ if self.rdlen is None:
+ self.rdlen = int(len(name_wire) / 2 + 16 + len(mac) / 2 + \
+ len(other_data) / 2)
+ self.dump_header(f, self.rdlen)
f.write('# Algorithm=%s Time-Signed=%d Fudge=%d\n' %
(self.algorithm, self.time_signed, self.fudge))
f.write('%s %012x %04x\n' % (name_wire, self.time_signed, self.fudge))
@@ -501,7 +561,8 @@ def get_config_param(section):
config_param = {'name' : (Name, {}),
'header' : (DNSHeader, header_xtables),
'question' : (DNSQuestion, question_xtables),
- 'edns' : (EDNS, {}), 'soa' : (SOA, {}), 'txt' : (TXT, {}),
+ 'edns' : (EDNS, {}), 'a' : (A, {}), 'ns' : (NS, {}),
+ 'soa' : (SOA, {}), 'txt' : (TXT, {}),
'rp' : (RP, {}), 'rrsig' : (RRSIG, {}),
'nsec' : (NSEC, {}), 'nsec3' : (NSEC3, {}),
'tsig' : (TSIG, {}) }
diff --git a/src/lib/dns/tests/testdata/message_fromWire12.spec b/src/lib/dns/tests/testdata/message_fromWire12.spec
new file mode 100644
index 0000000..4eadeed
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire12.spec
@@ -0,0 +1,21 @@
+#
+# A simple DNS response message with TSIG signed, but the owner name of TSIG
+# is compressed
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+rr_name: ptr=12
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/message_fromWire13.spec b/src/lib/dns/tests/testdata/message_fromWire13.spec
new file mode 100644
index 0000000..e81ec4c
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire13.spec
@@ -0,0 +1,20 @@
+#
+# Invalid TSIG: containing 2 TSIG RRs.
+#
+
+[custom]
+sections: header:question:tsig:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 2
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/message_fromWire14.spec b/src/lib/dns/tests/testdata/message_fromWire14.spec
new file mode 100644
index 0000000..bf68a93
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire14.spec
@@ -0,0 +1,21 @@
+#
+# Invalid TSIG: not in the additional section.
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+# TSIG goes to the answer section
+ancount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/message_fromWire15.spec b/src/lib/dns/tests/testdata/message_fromWire15.spec
new file mode 100644
index 0000000..25d810f
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire15.spec
@@ -0,0 +1,22 @@
+#
+# Invalid TSIG: not at the end of the message
+#
+
+[custom]
+sections: header:question:tsig:edns
+[header]
+id: 0x2d65
+rd: 1
+arcount: 2
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65
+[edns]
+# (all default)
diff --git a/src/lib/dns/tests/testdata/message_fromWire16.spec b/src/lib/dns/tests/testdata/message_fromWire16.spec
new file mode 100644
index 0000000..be0abc3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_fromWire16.spec
@@ -0,0 +1,21 @@
+#
+# Invalid TSIG: not in the additional section.
+#
+
+[custom]
+sections: header:question:tsig
+[header]
+id: 0x2d65
+rd: 1
+arcount: 1
+[question]
+name: www.example.com
+[tsig]
+as_rr: True
+rr_class: IN
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 0x4da8877a
+mac_size: 16
+mac: 0x227026ad297beee721ce6c6fff1e9ef3
+original_id: 0x2d65
diff --git a/src/lib/dns/tests/testdata/message_toText1.spec b/src/lib/dns/tests/testdata/message_toText1.spec
new file mode 100644
index 0000000..b31310e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toText1.spec
@@ -0,0 +1,24 @@
+#
+# A standard DNS message (taken from an invocation of dig)
+#
+
+[custom]
+sections: header:question:a/1:ns:a/2
+[header]
+id: 29174
+qr: 1
+aa: 1
+ancount: 1
+nscount: 1
+arcount: 1
+[question]
+name: www.example.com
+[a/1]
+as_rr: True
+rr_name: www.example.com
+address: 192.0.2.80
+[ns]
+as_rr: True
+[a/2]
+as_rr: True
+rr_name: ns.example.com
diff --git a/src/lib/dns/tests/testdata/message_toText1.txt b/src/lib/dns/tests/testdata/message_toText1.txt
new file mode 100644
index 0000000..58c7239
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toText1.txt
@@ -0,0 +1,14 @@
+;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29174
+;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1
+
+;; QUESTION SECTION:
+;www.example.com. IN A
+
+;; ANSWER SECTION:
+www.example.com. 86400 IN A 192.0.2.80
+
+;; AUTHORITY SECTION:
+example.com. 86400 IN NS ns.example.com.
+
+;; ADDITIONAL SECTION:
+ns.example.com. 86400 IN A 192.0.2.1
diff --git a/src/lib/dns/tests/testdata/message_toText2.spec b/src/lib/dns/tests/testdata/message_toText2.spec
new file mode 100644
index 0000000..978aab3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toText2.spec
@@ -0,0 +1,14 @@
+#
+# A standard DNS message with EDNS (taken from an invocation of dig)
+#
+
+[custom]
+sections: header:question:edns
+[header]
+id: 45981
+qr: 1
+rcode: refused
+arcount: 1
+[question]
+[edns]
+do: 1
diff --git a/src/lib/dns/tests/testdata/message_toText2.txt b/src/lib/dns/tests/testdata/message_toText2.txt
new file mode 100644
index 0000000..42cc2c1
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toText2.txt
@@ -0,0 +1,8 @@
+;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 45981
+;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
+
+;; OPT PSEUDOSECTION:
+; EDNS: version: 0, flags: do; udp: 4096
+
+;; QUESTION SECTION:
+;example.com. IN A
diff --git a/src/lib/dns/tests/testdata/message_toText3.spec b/src/lib/dns/tests/testdata/message_toText3.spec
new file mode 100644
index 0000000..a74ea1b
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toText3.spec
@@ -0,0 +1,31 @@
+#
+# A standard DNS message with TSIG (taken from an invocation of dig)
+#
+
+[custom]
+sections: header:question:a/1:ns:a/2:tsig
+[header]
+id: 10140
+qr: 1
+aa: 1
+ancount: 1
+nscount: 1
+arcount: 2
+[question]
+name: www.example.com
+[a/1]
+as_rr: True
+rr_name: www.example.com
+address: 192.0.2.80
+[ns]
+as_rr: True
+[a/2]
+as_rr: True
+rr_name: ns.example.com
+[tsig]
+as_rr: True
+rr_name: www.example.com
+algorithm: hmac-md5
+time_signed: 1304384318
+original_id: 10140
+mac: 0x5257c80396f2fa95b20c77ae9a652fb2
diff --git a/src/lib/dns/tests/testdata/message_toText3.txt b/src/lib/dns/tests/testdata/message_toText3.txt
new file mode 100644
index 0000000..359b9c5
--- /dev/null
+++ b/src/lib/dns/tests/testdata/message_toText3.txt
@@ -0,0 +1,17 @@
+;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 10140
+;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 2
+
+;; QUESTION SECTION:
+;www.example.com. IN A
+
+;; ANSWER SECTION:
+www.example.com. 86400 IN A 192.0.2.80
+
+;; AUTHORITY SECTION:
+example.com. 86400 IN NS ns.example.com.
+
+;; ADDITIONAL SECTION:
+ns.example.com. 86400 IN A 192.0.2.1
+
+;; TSIG PSEUDOSECTION:
+www.example.com. 0 ANY TSIG hmac-md5.sig-alg.reg.int. 1304384318 300 16 UlfIA5by+pWyDHeummUvsg== 10140 NOERROR 0
diff --git a/src/lib/dns/tests/tsigrecord_unittest.cc b/src/lib/dns/tests/tsigrecord_unittest.cc
index c1223ee..a932b7f 100644
--- a/src/lib/dns/tests/tsigrecord_unittest.cc
+++ b/src/lib/dns/tests/tsigrecord_unittest.cc
@@ -19,8 +19,10 @@
#include <util/buffer.h>
+#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
#include <dns/name.h>
+#include <dns/rdata.h>
#include <dns/rdataclass.h>
#include <dns/tsig.h>
#include <dns/tsigkey.h>
@@ -39,14 +41,16 @@ class TSIGRecordTest : public ::testing::Test {
protected:
TSIGRecordTest() :
test_name("www.example.com"), test_mac(16, 0xda),
- test_record(test_name, any::TSIG(TSIGKey::HMACMD5_NAME(), 0x4da8877a,
- TSIGContext::DEFAULT_FUDGE,
- test_mac.size(), &test_mac[0],
- 0x2d65, 0, 0, NULL)),
+ test_rdata(any::TSIG(TSIGKey::HMACMD5_NAME(), 0x4da8877a,
+ TSIGContext::DEFAULT_FUDGE,
+ test_mac.size(), &test_mac[0],
+ 0x2d65, 0, 0, NULL)),
+ test_record(test_name, test_rdata),
buffer(0), renderer(buffer)
{}
const Name test_name;
vector<unsigned char> test_mac;
+ const any::TSIG test_rdata;
const TSIGRecord test_record;
OutputBuffer buffer;
MessageRenderer renderer;
@@ -66,6 +70,42 @@ TEST_F(TSIGRecordTest, getLength) {
EXPECT_EQ(85, test_record.getLength());
}
+TEST_F(TSIGRecordTest, fromParams) {
+ // Construct the same TSIG RR as test_record from parameters.
+ // See the getLength test for the magic number of 85 (although it
+ // actually doesn't matter)
+ const TSIGRecord record(test_name, TSIGRecord::getClass(),
+ TSIGRecord::getTTL(), test_rdata, 85);
+ // Perform straight sanity checks
+ EXPECT_EQ(test_name, record.getName());
+ EXPECT_EQ(85, record.getLength());
+ EXPECT_EQ(0, test_rdata.compare(record.getRdata()));
+
+ // The constructor doesn't check the length...
+ EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+ TSIGRecord::getTTL(), test_rdata, 82));
+ // ...even for impossibly small values...
+ EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+ TSIGRecord::getTTL(), test_rdata, 1));
+ // ...or too large values.
+ EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+ TSIGRecord::getTTL(), test_rdata, 65536));
+
+ // RDATA must indeed be TSIG
+ EXPECT_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+ TSIGRecord::getTTL(), in::A("192.0.2.1"), 85),
+ DNSMessageFORMERR);
+
+ // Unexpected class
+ EXPECT_THROW(TSIGRecord(test_name, RRClass::IN(), TSIGRecord::getTTL(),
+ test_rdata, 85),
+ DNSMessageFORMERR);
+
+ // Unexpected TTL (simply ignored)
+ EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(),
+ RRTTL(3600), test_rdata, 85));
+}
+
TEST_F(TSIGRecordTest, recordToWire) {
UnitTestUtil::readWireData("tsigrecord_toWire1.wire", data);
EXPECT_EQ(1, test_record.toWire(renderer));
diff --git a/src/lib/dns/tsigrecord.cc b/src/lib/dns/tsigrecord.cc
index 503ee2c..40ea6c2 100644
--- a/src/lib/dns/tsigrecord.cc
+++ b/src/lib/dns/tsigrecord.cc
@@ -17,12 +17,14 @@
#include <util/buffer.h>
+#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
#include <dns/rrclass.h>
#include <dns/rrttl.h>
#include <dns/tsigrecord.h>
using namespace isc::util;
+using namespace isc::dns::rdata;
namespace {
// Internally used constants:
@@ -50,11 +52,45 @@ TSIGRecord::TSIGRecord(const Name& key_name,
rdata_.getMACSize() + rdata_.getOtherLen())
{}
+namespace {
+// This is a straightforward wrapper of dynamic_cast<const any::TSIG&>.
+// We use this so that we can throw the DNSMessageFORMERR exception when
+// unexpected type of RDATA is detected in the member initialization list
+// of the constructor below.
+const any::TSIG&
+castToTSIGRdata(const rdata::Rdata& rdata) {
+ try {
+ return (dynamic_cast<const any::TSIG&>(rdata));
+ } catch (std::bad_cast&) {
+ isc_throw(DNSMessageFORMERR,
+ "TSIG record is being constructed from "
+ "incompatible RDATA:" << rdata.toText());
+ }
+}
+}
+
+TSIGRecord::TSIGRecord(const Name& name, const RRClass& rrclass,
+ const RRTTL&, // we ignore TTL
+ const rdata::Rdata& rdata,
+ size_t length) :
+ key_name_(name), rdata_(castToTSIGRdata(rdata)), length_(length)
+{
+ if (rrclass != getClass()) {
+ isc_throw(DNSMessageFORMERR, "Unexpected TSIG RR class: " << rrclass);
+ }
+}
+
const RRClass&
TSIGRecord::getClass() {
return (RRClass::ANY());
}
+const RRTTL&
+TSIGRecord::getTTL() {
+ static RRTTL ttl(TSIG_TTL);
+ return (ttl);
+}
+
namespace {
template <typename OUTPUT>
void
diff --git a/src/lib/dns/tsigrecord.h b/src/lib/dns/tsigrecord.h
index 7688a36..e42edf1 100644
--- a/src/lib/dns/tsigrecord.h
+++ b/src/lib/dns/tsigrecord.h
@@ -20,6 +20,8 @@
#include <boost/shared_ptr.hpp>
+#include <util/buffer.h>
+
#include <dns/name.h>
#include <dns/rdataclass.h>
@@ -59,12 +61,95 @@ class AbstractMessageRenderer;
/// similar to why \c EDNS is a separate class.
class TSIGRecord {
public:
+ ///
+ /// \name Constructors
+ ///
+ /// We use the default copy constructor, default copy assignment operator,
+ /// (and default destructor) intentionally.
+ //@{
/// Constructor from TSIG key name and RDATA
///
/// \exception std::bad_alloc Resource allocation for copying the name or
/// RDATA fails
TSIGRecord(const Name& key_name, const rdata::any::TSIG& tsig_rdata);
+ /// Constructor from resource record (RR) parameters.
+ ///
+ /// This constructor is intended to be used in the context of parsing
+ /// an incoming DNS message that contains a TSIG. The parser would
+ /// first extract the owner name, RR type (which is TSIG) class, TTL and
+ /// the TSIG RDATA from the message. This constructor is expected to
+ /// be given these RR parameters (except the RR type, because it must be
+ /// TSIG).
+ ///
+ /// According to RFC2845, a TSIG RR uses fixed RR class (ANY) and TTL (0).
+ /// If the RR class is different from the expected one, this
+ /// implementation considers it an invalid record and throws an exception
+ /// of class \c DNSMessageFORMERR. On the other hand, the TTL is simply
+ /// ignored (in that sense we could even omit that parameter, but it's
+ /// still included if and when we want to change the policy). RFC2845
+ /// is silent about what the receiver should do if it sees an unexpected
+ /// RR class or TTL in a TSIG RR. This implementation simply follows what
+ /// BIND 9 does (it is not clear why BIND 9 employs the "inconsistent"
+ /// policy).
+ ///
+ /// Likewise, if \c rdata is not of type \c any::TSIG, an exception of
+ /// class DNSMessageFORMERR will be thrown. When the caller is a
+ /// DNS message parser and builds \c rdata from incoming wire format
+ /// data as described above, this case happens when the RR class is
+ /// different from ANY (in the implementation, the type check takes place
+ /// before the explicit check against the RR class explained in the
+ /// previous paragraph).
+ ///
+ /// The \c length parameter is intended to be the length of the TSIG RR
+ /// (from the beginning of the owner name to the end of the RDATA) when
+ /// the caller is a DNS message parser. Note that it is the actual length
+ /// for the RR in the format; if the owner name or the algorithm name
+ /// (in the RDATA) is compressed (although the latter should not be
+ /// compressed according to RFC3597), the length must be the size of the
+ /// compressed data. The length is recorded inside the class and will
+ /// be returned via subsequent calls to \c getLength(). It's intended to
+ /// be used in the context TSIG verification; in the verify process
+ /// the MAC computation must be performed for the original data without
+ /// TSIG, so, to avoid parsing the entire data in the verify process
+ /// again, it's necessary to record information that can identify the
+ /// length to be digested for the MAC. This parameter serves for that
+ /// purpose.
+ ///
+ /// \note Since the constructor doesn't take the wire format data per se,
+ /// it doesn't (and cannot) check the validity of \c length, and simply
+ /// accepts any given value. It even accepts obviously invalid values
+ /// such as 0. It's caller's responsibility to provide a valid value of
+ /// length, and, the verifier's responsibility to use the length safely.
+ ///
+ /// <b>DISCUSSION:</b> this design is fragile in that it introduces
+ /// a tight coupling between message parsing and TSIG verification via
+ /// the \c TSIGRecord class. In terms of responsibility decoupling,
+ /// the ideal way to have \c TSIGRecord remember the entire wire data
+ /// along with the length of the TSIG. Then in the TSIG verification
+ /// we could refer to the necessary potion of data solely from a
+ /// \c TSIGRecord object. However, this approach would require expensive
+ /// heavy copy of the original data or introduce another kind of coupling
+ /// between the data holder and this class (if the original data is freed
+ /// while a \c TSIGRecord object referencing the data still exists, the
+ /// result will be catastrophic). As a "best current compromise", we
+ /// use the current design. We may reconsider it if it turns out to
+ /// cause a big problem or we come up with a better idea.
+ ///
+ /// \exception DNSMessageFORMERR Given RR parameters are invalid for TSIG.
+ /// \exception std::bad_alloc Internal resource allocation fails.
+ ///
+ /// \param name The owner name of the TSIG RR
+ /// \param rrclass The RR class of the RR. Must be \c RRClass::ANY()
+ /// (see above)
+ /// \param ttl The TTL of the RR. Expected to be a zero TTL, but
+ /// actually ignored in this implementation.
+ /// \param rdata The RDATA of the RR. Must be of type \c any::TSIG.
+ /// \param length The size of the RR (see above)
+ TSIGRecord(const Name& name, const RRClass& rrclass, const RRTTL& ttl,
+ const rdata::Rdata& rdata, size_t length);
+ //@}
+
/// Return the owner name of the TSIG RR, which is the TSIG key name
///
/// \exception None
@@ -87,6 +172,16 @@ public:
/// \exception None
static const RRClass& getClass();
+ /// Return the TTL value of TSIG
+ ///
+ /// TSIG always uses 0 TTL. This static method returns it,
+ /// when, though unlikely, an application wants to know the TTL TSIG
+ /// is supposed to use.
+ ///
+ /// \exception None
+ static const RRTTL& getTTL();
+ //@}
+
/// Return the length of the TSIG record
///
/// When constructed from the key name and RDATA, it is the length of
@@ -94,8 +189,7 @@ public:
///
/// \note When constructed "from wire", that will mean the length of
/// the wire format data for the TSIG RR. The length will be necessary
- /// to verify the message once parse is completed. But this part is not
- /// implemented yet.
+ /// to verify the message once parse is completed.
///
/// \exception None
size_t getLength() const { return (length_); }
@@ -177,7 +271,7 @@ typedef boost::shared_ptr<const TSIGRecord> ConstTSIGRecordPtr;
///
/// \param os A \c std::ostream object on which the insertion operation is
/// performed.
-/// \param record An \c TSIGRecord object output by the operation.
+/// \param record A \c TSIGRecord object output by the operation.
/// \return A reference to the same \c std::ostream object referenced by
/// parameter \c os after the insertion operation.
std::ostream& operator<<(std::ostream& os, const TSIGRecord& record);
diff --git a/src/lib/util/unittests/Makefile.am b/src/lib/util/unittests/Makefile.am
index 84d7d21..340cd1f 100644
--- a/src/lib/util/unittests/Makefile.am
+++ b/src/lib/util/unittests/Makefile.am
@@ -4,5 +4,7 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
lib_LTLIBRARIES = libutil_unittests.la
libutil_unittests_la_SOURCES = fork.h fork.cc resolver.h
libutil_unittests_la_SOURCES += newhook.h newhook.cc
+libutil_unittests_la_SOURCES += testdata.h testdata.cc
+libutil_unittests_la_SOURCES += textdata.h
CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/util/unittests/testdata.cc b/src/lib/util/unittests/testdata.cc
new file mode 100644
index 0000000..2148d31
--- /dev/null
+++ b/src/lib/util/unittests/testdata.cc
@@ -0,0 +1,61 @@
+// Copyright (C) 2011 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 <string>
+#include <stdexcept>
+#include <fstream>
+#include <vector>
+
+#include "testdata.h"
+
+using namespace std;
+
+namespace {
+vector<string>&
+getDataPaths() {
+ static vector<string> data_path;
+ return (data_path);
+}
+}
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+void
+addTestDataPath(const string& path) {
+ getDataPaths().push_back(path);
+}
+
+void
+openTestData(const char* const datafile, ifstream& ifs) {
+ vector<string>::const_iterator it = getDataPaths().begin();
+ for (; it != getDataPaths().end(); ++it) {
+ string data_path = *it;
+ if (data_path.empty() || *data_path.rbegin() != '/') {
+ data_path.push_back('/');
+ }
+ ifs.open((data_path + datafile).c_str(), ios_base::in);
+ if (!ifs.fail()) {
+ return;
+ }
+ }
+
+ throw runtime_error("failed to open data file in data paths: " +
+ string(datafile));
+}
+
+}
+}
+}
diff --git a/src/lib/util/unittests/testdata.h b/src/lib/util/unittests/testdata.h
new file mode 100644
index 0000000..ed2722e
--- /dev/null
+++ b/src/lib/util/unittests/testdata.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2011 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 __UTIL_UNITTESTS_TESTDATA_H
+#define __UTIL_UNITTESTS_TESTDATA_H 1
+
+/**
+ * @file testdata.h
+ * @short Manipulating test data files.
+ *
+ * This utility defines functions that help test case handle test data
+ * stored in a file.
+ */
+
+namespace isc {
+namespace util {
+namespace unittests {
+/// Add a path (directory) that \c openTestData() will search for test data
+/// files.
+void addTestDataPath(const std::string& path);
+
+/// Open a file specified by 'datafile' using the data paths registered via
+/// addTestDataPath(). On success, ifs will be ready for reading the data
+/// stored in 'datafile'. If the data file cannot be open with any of the
+/// registered paths, a runtime_error exception will be thrown.
+void openTestData(const char* const datafile, std::ifstream& ifs);
+}
+}
+}
+
+#endif // __UTIL_UNITTESTS_TESTDATA_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/unittests/textdata.h b/src/lib/util/unittests/textdata.h
new file mode 100644
index 0000000..3e9b1aa
--- /dev/null
+++ b/src/lib/util/unittests/textdata.h
@@ -0,0 +1,103 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <istream>
+#include <string>
+#include <sstream>
+
+#include <gtest/gtest.h>
+
+#ifndef __UTIL_UNITTESTS_TEXTDATA_H
+#define __UTIL_UNITTESTS_TEXTDATA_H 1
+
+/**
+ * @file textdata.h
+ * @short Utilities for tests with text data.
+ *
+ * This utility provides convenient helper functions for unit tests using
+ * textual data.
+ */
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/// Line-by-line text comparison.
+///
+/// This templated function takes two standard input streams, extracts
+/// strings from them, and compares the two sets of strings line by line.
+template <typename EXPECTED_STREAM, typename ACTUAL_STREAM>
+void
+matchTextData(EXPECTED_STREAM& expected, ACTUAL_STREAM& actual) {
+ std::string actual_line;
+ std::string expected_line;
+ while (std::getline(actual, actual_line), !actual.eof()) {
+ std::getline(expected, expected_line);
+ if (expected.eof()) {
+ FAIL() << "Redundant line in actual output: " << actual_line;
+ break;
+ }
+ if (actual.bad() || actual.fail() ||
+ expected.bad() || expected.fail()) {
+ throw std::runtime_error("Unexpected error in data streams");
+ }
+ EXPECT_EQ(expected_line, actual_line);
+ }
+ while (std::getline(expected, expected_line), !expected.eof()) {
+ ADD_FAILURE() << "Missing line in actual output: " << expected_line;
+ }
+}
+
+/// Similar to the fully templated version, but takes string for the second
+/// (actual) data.
+///
+/// Due to the nature of textual data, it will often be the case that test
+/// data is given as a string object. This shortcut version helps such cases
+/// so that the test code doesn't have to create a string stream with the
+/// string data just for testing.
+template <typename EXPECTED_STREAM>
+void
+matchTextData(EXPECTED_STREAM& expected, const std::string& actual_text) {
+ std::istringstream iss(actual_text);
+ matchTextData(expected, iss);
+}
+
+/// Same for the previous version, but the first argument is string.
+template <typename ACTUAL_STREAM>
+void
+matchTextData(const std::string& expected_text, ACTUAL_STREAM& actual) {
+ std::istringstream iss(expected_text);
+ matchTextData(iss, actual);
+}
+
+/// Same for the previous two, but takes strings for both expected and
+/// actual data.
+void
+matchTextData(const std::string& expected_text,
+ const std::string& actual_text)
+{
+ std::istringstream expected_is(expected_text);
+ std::istringstream actual_is(actual_text);
+ matchTextData(expected_is, actual_is);
+}
+
+}
+}
+}
+
+#endif // __UTIL_UNITTESTS_TEXTDATA_H
+
+// Local Variables:
+// mode: c++
+// End:
More information about the bind10-changes
mailing list