diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am index e9333e2f76..807726a615 100644 --- a/src/lib/dhcp/Makefile.am +++ b/src/lib/dhcp/Makefile.am @@ -37,6 +37,7 @@ libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h libb10_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS) libb10_dhcp___la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES) libb10_dhcp___la_LIBADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la +libb10_dhcp___la_LIBADD += $(top_builddir)/src/lib/dns/libb10-dns++.la libb10_dhcp___la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la libb10_dhcp___la_LDFLAGS = -no-undefined -version-info 2:0:0 diff --git a/src/lib/dhcp/option_data_types.cc b/src/lib/dhcp/option_data_types.cc index 46aa663f38..f002ba8778 100644 --- a/src/lib/dhcp/option_data_types.cc +++ b/src/lib/dhcp/option_data_types.cc @@ -13,6 +13,8 @@ // PERFORMANCE OF THIS SOFTWARE. #include +#include +#include #include namespace isc { @@ -206,6 +208,48 @@ OptionDataTypeUtil::writeBool(const bool value, buf.push_back(static_cast(value ? 1 : 0)); } +std::string +OptionDataTypeUtil::readFqdn(const std::vector& buf) { + // If buffer is empty emit an error. + if (buf.empty()) { + isc_throw(BadDataTypeCast, "unable to read FQDN from a buffer." + << " The buffer is empty"); + } + // Copy the data from a buffer to InputBuffer so as we can use + // isc::dns::Name object to get the FQDN. This is not the most + // efficient way to do it but currently there is no construtor + // in Name that would use std::vector directly. + isc::util::InputBuffer in_buf(static_cast(&buf[0]), buf.size()); + try { + // Try to create an object from the buffer. If exception is thrown + // it means that the buffer doesn't hold a valid domain name (invalid + // syntax). + isc::dns::Name name(in_buf); + return (name.toText()); + } catch (const isc::Exception& ex) { + // Unable to convert the data in the buffer into FQDN. + isc_throw(BadDataTypeCast, ex.what()); + } +} + +void +OptionDataTypeUtil::writeFqdn(const std::string& fqdn, + std::vector& buf) { + try { + isc::dns::Name name(fqdn); + isc::dns::LabelSequence labels(name); + if (labels.getDataLength() > 0) { + buf.resize(buf.size() + labels.getDataLength()); + size_t read_len = 0; + const uint8_t* data = labels.getData(&read_len); + memcpy(static_cast(&buf[buf.size() - labels.getDataLength()]), + data, read_len); + } + } catch (const isc::Exception& ex) { + isc_throw(BadDataTypeCast, ex.what()); + } +} + std::string OptionDataTypeUtil::readString(const std::vector& buf) { std::string value; diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h index afc6d9352f..f0248eb1b6 100644 --- a/src/lib/dhcp/option_data_types.h +++ b/src/lib/dhcp/option_data_types.h @@ -332,6 +332,33 @@ public: } } + /// @brief Read FQDN from a buffer as a string value. + /// + /// The format of an FQDN within a buffer complies with RFC1035, + /// section 3.1. + /// + /// @param buf input buffer holding a FQDN. + /// + /// @throw BadDataTypeCast if a FQDN stored within a buffer is + /// invalid (e.g. empty, contains invalid characters, truncated). + /// @return fully qualified domain name in a text form. + static std::string readFqdn(const std::vector& buf); + + /// @brief Append FQDN into a buffer. + /// + /// This method appends the Fully Qualified Domain Name (FQDN) + /// represented as string value into a buffer. The format of + /// the FQDN being stored into a buffer complies with RFC1035, + /// section 3.1. + /// + /// @param fqdn fully qualified domain name to be written. + /// @param [out] buf output buffer. + /// + /// @throw isc::dhcp::BadDataTypeCast if provided FQDN + /// is invalid. + static void writeFqdn(const std::string& fqdn, + std::vector& buf); + /// @brief Read string value from a buffer. /// /// @param buf input buffer. diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am index e66d700903..5799d58540 100644 --- a/src/lib/dhcp/tests/Makefile.am +++ b/src/lib/dhcp/tests/Makefile.am @@ -35,6 +35,7 @@ libdhcp___unittests_SOURCES += option6_ia_unittest.cc libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc libdhcp___unittests_SOURCES += option6_int_array_unittest.cc libdhcp___unittests_SOURCES += option6_int_unittest.cc +libdhcp___unittests_SOURCES += option_data_types_unittest.cc libdhcp___unittests_SOURCES += option_definition_unittest.cc libdhcp___unittests_SOURCES += option_custom_unittest.cc libdhcp___unittests_SOURCES += option_unittest.cc diff --git a/src/lib/dhcp/tests/option_data_types_unittest.cc b/src/lib/dhcp/tests/option_data_types_unittest.cc new file mode 100644 index 0000000000..e18ec243a5 --- /dev/null +++ b/src/lib/dhcp/tests/option_data_types_unittest.cc @@ -0,0 +1,144 @@ +// Copyright (C) 2012 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 +#include +#include + +using namespace isc; +using namespace isc::dhcp; + +namespace { + +/// @brief Test class for option data type utilities. +class OptionDataTypesTest : public ::testing::Test { +public: + + /// @brief Constructor. + OptionDataTypesTest() { } + +}; + +// The purpose of this test is to verify that FQDN is read from +// a buffer and returned as a text. The representation of the FQDN +// in the buffer complies with RFC1035, section 3.1. +// This test also checks that if invalid (truncated) FQDN is stored +// in a buffer the appropriate exception is returned when trying to +// read it as a string. +TEST_F(OptionDataTypesTest, readFqdn) { + // The binary representation of the "mydomain.example.com". + // Values: 8, 7, 3 and 0 specify the lengths of subsequent + // labels within the FQDN. + const char data[] = { + 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain" + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0 + }; + + // Make a vector out of the data. + std::vector buf(data, data + sizeof(data)); + + // Read the buffer as FQDN and verify its correctness. + std::string fqdn; + EXPECT_NO_THROW(fqdn = OptionDataTypeUtil::readFqdn(buf)); + EXPECT_EQ("mydomain.example.com.", fqdn); + + // By resizing the buffer we simulate truncation. The first + // length field (8) indicate that the first label's size is + // 8 but the actual buffer size is 5. Expect that conversion + // fails. + buf.resize(5); + EXPECT_THROW( + OptionDataTypeUtil::readFqdn(buf), + isc::dhcp::BadDataTypeCast + ); + + // Another special case: provide an empty buffer. + buf.clear(); + EXPECT_THROW( + OptionDataTypeUtil::readFqdn(buf), + isc::dhcp::BadDataTypeCast + ); +} + +// The purpose of this test is to verify that FQDN's syntax is validated +// and that FQDN is correctly written to a buffer in a format described +// in RFC1035 section 3.1. +TEST_F(OptionDataTypesTest, writeFqdn) { + // Create empty buffer. The FQDN will be written to it. + OptionBuffer buf; + // Write a domain name into the buffer in the format described + // in RFC1035 section 3.1. This function should not throw + // exception because domain name is well formed. + EXPECT_NO_THROW( + OptionDataTypeUtil::writeFqdn("mydomain.example.com", buf) + ); + // The length of the data is 22 (8 bytes for "mydomain" label, + // 7 bytes for "example" label, 3 bytes for "com" label and + // finally 4 bytes positions between labels where length + // information is stored. + ASSERT_EQ(22, buf.size()); + + // Verify that length fields between labels hold valid values. + EXPECT_EQ(8, buf[0]); // length of "mydomain" + EXPECT_EQ(7, buf[9]); // length of "example" + EXPECT_EQ(3, buf[17]); // length of "com" + EXPECT_EQ(0, buf[21]); // zero byte at the end. + + // Verify that labels are valid. + std::string label0(buf.begin() + 1, buf.begin() + 9); + EXPECT_EQ("mydomain", label0); + + std::string label1(buf.begin() + 10, buf.begin() + 17); + EXPECT_EQ("example", label1); + + std::string label2(buf.begin() + 18, buf.begin() + 21); + EXPECT_EQ("com", label2); + + // The tested function is supposed to append data to a buffer + // so let's check that it is a case by appending another domain. + OptionDataTypeUtil::writeFqdn("hello.net", buf); + + // The buffer length should be now longer. + ASSERT_EQ(33, buf.size()); + + // Check the length fields for new labels being appended. + EXPECT_EQ(5, buf[22]); + EXPECT_EQ(3, buf[28]); + + // And check that labels are ok. + std::string label3(buf.begin() + 23, buf.begin() + 28); + EXPECT_EQ("hello", label3); + + std::string label4(buf.begin() + 29, buf.begin() + 32); + EXPECT_EQ("net", label4); + + // Check that invalid (empty) FQDN is rejected and expected + // exception type is thrown. + buf.clear(); + EXPECT_THROW( + OptionDataTypeUtil::writeFqdn("", buf), + isc::dhcp::BadDataTypeCast + ); + + // Check another invalid domain name (with repeated dot). + buf.clear(); + EXPECT_THROW( + OptionDataTypeUtil::writeFqdn("example..com", buf), + isc::dhcp::BadDataTypeCast + ); +} + +} // anonymous namespace