diff options
-rw-r--r-- | src/lib/dns/messagerenderer.cc | 51 | ||||
-rw-r--r-- | src/lib/dns/messagerenderer.h | 44 | ||||
-rw-r--r-- | src/lib/dns/tests/messagerenderer_unittest.cc | 45 | ||||
-rw-r--r-- | src/lib/dns/tests/testdata/name_toWire5 | 12 | ||||
-rw-r--r-- | src/lib/dns/tests/testdata/name_toWire5.spec | 19 | ||||
-rw-r--r-- | src/lib/dns/tests/testdata/name_toWire6 | 12 | ||||
-rw-r--r-- | src/lib/dns/tests/testdata/name_toWire6.spec | 19 |
7 files changed, 190 insertions, 12 deletions
diff --git a/src/lib/dns/messagerenderer.cc b/src/lib/dns/messagerenderer.cc index 3b0b6246b8..a5bff63afe 100644 --- a/src/lib/dns/messagerenderer.cc +++ b/src/lib/dns/messagerenderer.cc @@ -34,9 +34,14 @@ namespace { // hide internal-only names from the public namespaces /// objects, and searches the set for the position of the longest match /// (ancestor) name against each new name to be rendered into the buffer. struct NameCompressNode { - NameCompressNode(const OutputBuffer& buffer, const size_t pos, + NameCompressNode(const MessageRenderer& renderer, + const OutputBuffer& buffer, const size_t pos, const size_t len) : - buffer_(buffer), pos_(pos), len_(len) {} + renderer_(renderer), buffer_(buffer), pos_(pos), len_(len) {} + /// The renderer that performs name compression using the node. + /// This is kept in each node to detect the compression mode + /// (case-sensitive or not) in the comparison functor (\c NameCompare). + const MessageRenderer& renderer_; /// The buffer in which the corresponding name is rendered. const OutputBuffer& buffer_; /// The position (offset from the beginning) in the buffer where the @@ -78,6 +83,9 @@ struct NameCompare : public std::binary_function<NameCompressNode, return (false); } + const bool case_sensitive = + (n1.renderer_.getCompressMode() == MessageRenderer::CASE_SENSITIVE); + uint16_t pos1 = n1.pos_; uint16_t pos2 = n2.pos_; uint16_t l1 = 0; @@ -85,10 +93,19 @@ struct NameCompare : public std::binary_function<NameCompressNode, for (uint16_t i = 0; i < n1.len_; i++, pos1++, pos2++) { pos1 = nextPosition(n1.buffer_, pos1, l1); pos2 = nextPosition(n2.buffer_, pos2, l2); - if (tolower(n1.buffer_[pos1]) < tolower(n2.buffer_[pos2])) { - return (true); - } else if (tolower(n1.buffer_[pos1]) > tolower(n2.buffer_[pos2])) { - return (false); + if (case_sensitive) { + if (n1.buffer_[pos1] < n2.buffer_[pos2]) { + return (true); + } else if (n1.buffer_[pos1] > n2.buffer_[pos2]) { + return (false); + } + } else { + if (tolower(n1.buffer_[pos1]) < tolower(n2.buffer_[pos2])) { + return (true); + } else if (tolower(n1.buffer_[pos1]) > + tolower(n2.buffer_[pos2])) { + return (false); + } } } @@ -130,14 +147,14 @@ private: /// The implementation is hidden from applications. We can refer to specific /// members of this class only within the implementation source file. /// -struct MessageRendererImpl { +struct MessageRenderer::MessageRendererImpl { /// \brief Constructor from an output buffer. /// /// \param buffer An \c OutputBuffer object to which wire format data is /// written. MessageRendererImpl(OutputBuffer& buffer) : buffer_(buffer), nbuffer_(Name::MAX_WIRE), msglength_limit_(512), - truncated_(false) + truncated_(false), compress_mode_(MessageRenderer::CASE_INSENSITIVE) {} /// The buffer that holds the entire DNS message. OutputBuffer& buffer_; @@ -154,6 +171,8 @@ struct MessageRendererImpl { /// A boolean flag that indicates truncation has occurred while rendering /// the data. bool truncated_; + /// The name compression mode. + CompressMode compress_mode_; }; MessageRenderer::MessageRenderer(OutputBuffer& buffer) : @@ -181,6 +200,7 @@ MessageRenderer::clear() { impl_->nodeset_.clear(); impl_->msglength_limit_ = 512; impl_->truncated_ = false; + impl_->compress_mode_ = CASE_INSENSITIVE; } void @@ -238,6 +258,16 @@ MessageRenderer::setTruncated() { impl_->truncated_ = true; } +MessageRenderer::CompressMode +MessageRenderer::getCompressMode() const { + return (impl_->compress_mode_); +} + +void +MessageRenderer::setCompressMode(const CompressMode mode) { + impl_->compress_mode_ = mode; +} + void MessageRenderer::writeName(const Name& name, const bool compress) { impl_->nbuffer_.clear(); @@ -254,7 +284,7 @@ MessageRenderer::writeName(const Name& name, const bool compress) { if (impl_->nbuffer_[i] == 0) { continue; } - n = impl_->nodeset_.find(NameCompressNode(impl_->nbuffer_, i, + n = impl_->nodeset_.find(NameCompressNode(*this, impl_->nbuffer_, i, impl_->nbuffer_.getLength() - i)); if (n != notfound) { @@ -283,7 +313,8 @@ MessageRenderer::writeName(const Name& name, const bool compress) { if (offset + j > Name::MAX_COMPRESS_POINTER) { break; } - impl_->nodeset_.insert(NameCompressNode(impl_->buffer_, offset + j, + impl_->nodeset_.insert(NameCompressNode(*this, impl_->buffer_, + offset + j, impl_->nbuffer_.getLength() - j)); } diff --git a/src/lib/dns/messagerenderer.h b/src/lib/dns/messagerenderer.h index f83293792a..bdcf663c02 100644 --- a/src/lib/dns/messagerenderer.h +++ b/src/lib/dns/messagerenderer.h @@ -22,7 +22,6 @@ namespace dns { // forward declarations class OutputBuffer; class Name; -class MessageRendererImpl; /// /// \brief The \c MessageRenderer class encapsulates implementation details @@ -38,7 +37,7 @@ class MessageRendererImpl; /// to care about this class. /// /// A \c MessageRenderer class object is constructed with a \c OutputBuffer -/// object, which is the buffer into which the rendered data will be written. +/// object, which is the buffer into which the rendered %data will be written. /// Normally the buffer is expected to be empty on construction, but it doesn't /// have to be so; the \c MessageRenderer object will start rendering from the /// end of the buffer at the time of construction. However, if the @@ -69,6 +68,34 @@ class MessageRendererImpl; /// abstraction and keep the definition simpler. class MessageRenderer { public: + /// \brief Compression mode constants. + /// + /// The \c CompressMode enum type represents the name compression mode + /// for the \c MessageRenderer. + /// \c CASE_INSENSITIVE means compress names in case-insensitive manner; + /// \c CASE_SENSITIVE means compress names in case-sensitive manner. + /// By default, \c MessageRenderer compresses names in case-insensitive + /// manner. + /// Compression mode can be dynamically modified by the + /// \c setCompressMode() method. + /// The mode can be changed even in the middle of rendering, although this + /// is not an intended usage. In this case the names already compressed + /// are intact; only names being compressed after the mode change are + /// affected by the change. + /// If the internal \c MessageRenderer is reinitialized by the \c clear() + /// method, the compression mode will be reset to the default, which is + /// \c CASE_INSENSITIVE + /// + /// One specific case where case-sensitive compression is required is + /// AXFR as described in draft-ietf-dnsext-axfr-clarify. A primary + /// authoritative DNS server implementation using this API would specify + /// \c CASE_SENSITIVE before rendering outgoing AXFR messages. + /// + enum CompressMode { + CASE_INSENSITIVE, //!< Compress names case-insensitive manner (default) + CASE_SENSITIVE //!< Compress names case-sensitive manner + }; +public: /// /// \name Constructors and Destructor //@{ @@ -116,6 +143,12 @@ public: /// /// \return The maximum length in bytes. size_t getLengthLimit() const; + /// \brief Return the compression mode of the \c MessageRenderer. + /// + /// This method never throws an exception. + /// + /// \return The current compression mode. + CompressMode getCompressMode() const; //@} /// @@ -134,6 +167,12 @@ public: /// /// \param len The maximum length in bytes. void setLengthLimit(size_t len); + /// \brief Set the compression mode of the \c MessageRenderer. + /// + /// This method never throws an exception. + /// + /// \param mode A \c CompressMode value representing the compression mode. + void setCompressMode(CompressMode mode); //@} /// @@ -220,6 +259,7 @@ public: /// \param compress A boolean indicating whether to enable name compression. void writeName(const Name& name, bool compress = true); private: + struct MessageRendererImpl; MessageRendererImpl* impl_; }; } diff --git a/src/lib/dns/tests/messagerenderer_unittest.cc b/src/lib/dns/tests/messagerenderer_unittest.cc index 197ee7c9e8..cc42f7125f 100644 --- a/src/lib/dns/tests/messagerenderer_unittest.cc +++ b/src/lib/dns/tests/messagerenderer_unittest.cc @@ -98,7 +98,25 @@ TEST_F(MessageRendererTest, writeNamePointerChain) { buffer.getLength(), &data[0], data.size()); } +TEST_F(MessageRendererTest, compressMode) { + // By default the render performs case insensitive compression. + EXPECT_EQ(MessageRenderer::CASE_INSENSITIVE, renderer.getCompressMode()); + + // The mode can be explicitly changed. + renderer.setCompressMode(MessageRenderer::CASE_SENSITIVE); + EXPECT_EQ(MessageRenderer::CASE_SENSITIVE, renderer.getCompressMode()); + renderer.setCompressMode(MessageRenderer::CASE_INSENSITIVE); + EXPECT_EQ(MessageRenderer::CASE_INSENSITIVE, renderer.getCompressMode()); + + // The clear() method resets the mode to the default. + renderer.setCompressMode(MessageRenderer::CASE_SENSITIVE); + renderer.clear(); + EXPECT_EQ(MessageRenderer::CASE_INSENSITIVE, renderer.getCompressMode()); +} + TEST_F(MessageRendererTest, writeNameCaseCompress) { + // By default MessageRenderer performs case insensitive compression. + UnitTestUtil::readWireData("testdata/name_toWire1", data); renderer.writeName(Name("a.example.com.")); // this should match the first name in terms of compression: @@ -108,6 +126,33 @@ TEST_F(MessageRendererTest, writeNameCaseCompress) { buffer.getLength(), &data[0], data.size()); } +TEST_F(MessageRendererTest, writeNameCaseSensitiveCompress) { + // name compression in case sensitive manner. See the data file + // description for details. + renderer.setCompressMode(MessageRenderer::CASE_SENSITIVE); + UnitTestUtil::readWireData("testdata/name_toWire5", data); + renderer.writeName(Name("a.example.com.")); + renderer.writeName(Name("b.eXample.com.")); + renderer.writeName(Name("c.eXample.com.")); + EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(), + buffer.getLength(), &data[0], data.size()); +} + +TEST_F(MessageRendererTest, writeNameMixedCaseCompress) { + renderer.setCompressMode(MessageRenderer::CASE_SENSITIVE); + UnitTestUtil::readWireData("testdata/name_toWire6", data); + renderer.writeName(Name("a.example.com.")); + renderer.writeName(Name("b.eXample.com.")); + + // Change the compression mode in the middle of rendering. This is an + // unusual operation and is unlikely to happen in practice, but is still + // allowed in this API. + renderer.setCompressMode(MessageRenderer::CASE_INSENSITIVE); + renderer.writeName(Name("c.b.EXAMPLE.com.")); + EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, buffer.getData(), + buffer.getLength(), &data[0], data.size()); +} + TEST_F(MessageRendererTest, writeRootName) { // root name is special: it never causes compression or can (reasonably) // be a compression pointer. So it makes sense to check this case diff --git a/src/lib/dns/tests/testdata/name_toWire5 b/src/lib/dns/tests/testdata/name_toWire5 new file mode 100644 index 0000000000..22cb686a5b --- /dev/null +++ b/src/lib/dns/tests/testdata/name_toWire5 @@ -0,0 +1,12 @@ +### +### This data file was auto-generated from name_toWire5.spec +### + +# DNS Name: a.example.com +0161076578616d706c6503636f6d00 + +# DNS Name: b.eXample + compression pointer: 10 +0162076558616d706c65 c00a + +# DNS Name: c + compression pointer: 17 +0163 c011 diff --git a/src/lib/dns/tests/testdata/name_toWire5.spec b/src/lib/dns/tests/testdata/name_toWire5.spec new file mode 100644 index 0000000000..87e140d43a --- /dev/null +++ b/src/lib/dns/tests/testdata/name_toWire5.spec @@ -0,0 +1,19 @@ +# +# A sequence of names that would be compressed case-sensitive manner. +# First name: "a.example.com" +# Second name: "b.eXample.com". Due to case-sensitive comparison only "com" +# can be compressed. +# Third name: "c.eXample.com". "eXample.com" part matches that of the second +# name and can be compressed. +# + +[custom] +sections: name/1:name/2:name/3 +[name/1] +name: a.example.com +[name/2] +name: b.eXample +pointer: 10 +[name/3] +name: c +pointer: 17 diff --git a/src/lib/dns/tests/testdata/name_toWire6 b/src/lib/dns/tests/testdata/name_toWire6 new file mode 100644 index 0000000000..49db876c6e --- /dev/null +++ b/src/lib/dns/tests/testdata/name_toWire6 @@ -0,0 +1,12 @@ +### +### This data file was auto-generated from name_toWire6.spec +### + +# DNS Name: a.example.com +0161076578616d706c6503636f6d00 + +# DNS Name: b.eXample + compression pointer: 10 +0162076558616d706c65 c00a + +# DNS Name: c + compression pointer: 15 +0163 c00f diff --git a/src/lib/dns/tests/testdata/name_toWire6.spec b/src/lib/dns/tests/testdata/name_toWire6.spec new file mode 100644 index 0000000000..a536f5da19 --- /dev/null +++ b/src/lib/dns/tests/testdata/name_toWire6.spec @@ -0,0 +1,19 @@ +# +# A sequence of names that would be compressed both case-sensitive and +# case-insensitive manner (unusual, but allowed). +# First and second name: see name_toWire5.spec. +# Third name: "c.b.EXAMPLE.com". This is rendered with case-insensitive +# compression, so "b.EXAMPLE.com" part of the name matches that of the +# second name. +# + +[custom] +sections: name/1:name/2:name/3 +[name/1] +name: a.example.com +[name/2] +name: b.eXample +pointer: 10 +[name/3] +name: c +pointer: 15 |