From: Marcin Siodelski Date: Thu, 29 Nov 2012 18:40:32 +0000 (+0100) Subject: [2491] Support for custom option to hold FQDN. X-Git-Tag: bind10-1.0.0-beta-release~19^2~15^2~26 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=84e441c75606aff87131e7d84010679a11f242ce;p=thirdparty%2Fkea.git [2491] Support for custom option to hold FQDN. --- diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc index 8b3ef11111..5c6bf6e77d 100644 --- a/src/lib/dhcp/option_custom.cc +++ b/src/lib/dhcp/option_custom.cc @@ -68,17 +68,27 @@ OptionCustom::createBuffers() { // For fixed-size data type such as boolean, integer, even // IP address we can use the utility function to get the required // buffer size. - int data_size = OptionDataTypeUtil::getDataTypeLen(*field); - - // For variable size types (such as string) the function above - // will return 0 so we need to do a runtime check. Since variable - // length data fields may be laid only at the end of an option we - // consume the rest of this option. Note that validate() function - // in OptionDefinition object should have checked whether the - // data fields layout is correct (that the variable string fields - // are laid at the end). + size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field); + + // For variable size types (e.g. string) the function above will + // return 0 so we need to do a runtime check of the length. if (data_size == 0) { - data_size = std::distance(data, data_.end()); + // FQDN is a special data type as it stores variable length data + // but the data length is encoded in the buffer. The easiest way + // to obtain the length of the data is to read the FQDN. The + // utility function will return the size of the buffer on success. + if (*field == OPT_FQDN_TYPE) { + OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_.end()), + data_size); + } else { + // In other case we are dealing with string or binary value + // which size can't be determined. Thus we consume the + // remaining part of the buffer for it. Note that variable + // size data can be laid at the end of the option only and + // that the validate() function in OptionDefinition object + // should have checked wheter it is a case for this option. + data_size = std::distance(data, data_.end()); + } if (data_size == 0) { // If we reached the end of buffer we assume that this option is // truncated because there is no remaining data to initialize @@ -105,7 +115,7 @@ OptionCustom::createBuffers() { // data fields of the same type. The type of those fields // is held in the data_type variable so let's use it to determine // a size of buffers. - int data_size = OptionDataTypeUtil::getDataTypeLen(data_type); + size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type); // The check below will fail if the input buffer is too short // for the data size being held by this option. // Note that data_size returned by getDataTypeLen may be zero @@ -117,27 +127,44 @@ OptionCustom::createBuffers() { // For an array of values we are taking different path because // we have to handle multiple buffers. if (definition_.getArrayType()) { - // We don't perform other checks for data types that can't be - // used together with array indicator such as strings, empty field - // etc. This is because OptionDefinition::validate function should - // have checked this already. Thus data_size must be greater than - // zero. - assert(data_size > 0); - // Get equal chunks of data and store as collection of buffers. - // Truncate any remaining part which length is not divisible by - // data_size. Note that it is ok to truncate the data if and only - // if the data buffer is long enough to keep at least one value. - // This has been checked above already. - do { + while (data != data_.end()) { + // FQDN is a special case because it is of a variable length. + // The actual length for a particular FQDN is encoded within + // a buffer so we have to actually read the FQDN from a buffer + // to get it. + if (data_type == OPT_FQDN_TYPE) { + OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_.end()), + data_size); + } + // We don't perform other checks for data types that can't be + // used together with array indicator such as strings, empty field + // etc. This is because OptionDefinition::validate function should + // have checked this already. Thus data_size must be greater than + // zero. + assert(data_size > 0); + // Get chunks of data and store as a collection of buffers. + // Truncate any remaining part which length is not divisible by + // data_size. Note that it is ok to truncate the data if and only + // if the data buffer is long enough to keep at least one value. + // This has been checked above already. + if (std::distance(data, data_.end()) < data_size) { + break; + } buffers.push_back(OptionBuffer(data, data + data_size)); data += data_size; - } while (std::distance(data, data_.end()) >= data_size); + } } else { // For non-arrays the data_size can be zero because // getDataTypeLen returns zero for variable size data types // such as strings. Simply take whole buffer. if (data_size == 0) { - data_size = std::distance(data, data_.end()); + // For FQDN we get the size by actually reading the FQDN. + if (data_type == OPT_FQDN_TYPE) { + OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_.end()), + data_size); + } else { + data_size = std::distance(data, data_.end()); + } } if (data_size > 0) { buffers.push_back(OptionBuffer(data, data + data_size)); @@ -185,6 +212,9 @@ OptionCustom::dataFieldToText(const OptionDataType data_type, case OPT_IPV6_ADDRESS_TYPE: text << readAddress(index).toText(); break; + case OPT_FQDN_TYPE: + text << readFqdn(index); + break; case OPT_STRING_TYPE: text << readString(index); break; @@ -268,6 +298,17 @@ OptionCustom::readBoolean(const uint32_t index) const { return (OptionDataTypeUtil::readBool(buffers_[index])); } +std::string +OptionCustom::readFqdn(const uint32_t index) const { + checkIndex(index); + try { + size_t len = 0; + return (OptionDataTypeUtil::readFqdn(buffers_[index], len)); + } catch (const Exception& ex) { + isc_throw(BadDataTypeCast, ex.what()); + } +} + std::string OptionCustom::readString(const uint32_t index) const { checkIndex(index); diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h index ad248c5f38..1962fe5c8d 100644 --- a/src/lib/dhcp/option_custom.h +++ b/src/lib/dhcp/option_custom.h @@ -105,6 +105,17 @@ public: /// @return read boolean value. bool readBoolean(const uint32_t index) const; + /// @brief Read a buffer as FQDN. + /// + /// @param index buffer index. + /// @param [out] len number of bytes read from a buffer. + /// + /// @throw isc::OutOfRange if buffer index is out of range. + /// @throw isc::dhcp::BadDataTypeCast if a buffer being read + /// does not hold a valid FQDN. + /// @return string representation if FQDN. + std::string readFqdn(const uint32_t index) const; + /// @brief Read a buffer as integer value. /// /// @param index buffer index. diff --git a/src/lib/dhcp/option_data_types.cc b/src/lib/dhcp/option_data_types.cc index f002ba8778..ad1491dd43 100644 --- a/src/lib/dhcp/option_data_types.cc +++ b/src/lib/dhcp/option_data_types.cc @@ -209,7 +209,10 @@ OptionDataTypeUtil::writeBool(const bool value, } std::string -OptionDataTypeUtil::readFqdn(const std::vector& buf) { +OptionDataTypeUtil::readFqdn(const std::vector& buf, + size_t& len) { + len = 0; + // If buffer is empty emit an error. if (buf.empty()) { isc_throw(BadDataTypeCast, "unable to read FQDN from a buffer." @@ -225,6 +228,7 @@ OptionDataTypeUtil::readFqdn(const std::vector& buf) { // it means that the buffer doesn't hold a valid domain name (invalid // syntax). isc::dns::Name name(in_buf); + len = name.getLength(); return (name.toText()); } catch (const isc::Exception& ex) { // Unable to convert the data in the buffer into FQDN. diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h index f0248eb1b6..58567cdb35 100644 --- a/src/lib/dhcp/option_data_types.h +++ b/src/lib/dhcp/option_data_types.h @@ -338,11 +338,13 @@ public: /// section 3.1. /// /// @param buf input buffer holding a FQDN. + /// @param [out] len number of bytes read from a buffer. /// /// @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); + static std::string readFqdn(const std::vector& buf, + size_t& len); /// @brief Append FQDN into a buffer. /// diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc index 34969ee864..9395ea5641 100644 --- a/src/lib/dhcp/tests/option_custom_unittest.cc +++ b/src/lib/dhcp/tests/option_custom_unittest.cc @@ -203,6 +203,39 @@ TEST_F(OptionCustomTest, booleanData) { ); } +// The purpose of this test is to verify that the data from a buffer +// can be read as FQDN. +TEST_F(OptionCustomTest, fqdnData) { + OptionDefinition opt_def("OPTION_FOO", 1000, "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, + }; + + std::vector buf(data, data + sizeof(data)); + + boost::scoped_ptr option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())); + ); + ASSERT_TRUE(option); + + ASSERT_EQ(1, option->getDataFieldsNum()); + + std::string domain0 = option->readFqdn(0); + EXPECT_EQ("mydomain.example.com.", domain0); + + // Check that the option with truncated data can't be created. + EXPECT_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, + buf.begin(), buf.begin() + 4)), + isc::dhcp::BadDataTypeCast + ); +} + // The purpose of this test is to verify that the option definition comprising // 16-bit signed integer value can be used to create an instance of custom option. TEST_F(OptionCustomTest, int16Data) { @@ -338,6 +371,7 @@ TEST_F(OptionCustomTest, ipv6AddressData) { ); } + // The purpose of this test is to verify that the option definition comprising // string value can be used to create an instance of custom option. TEST_F(OptionCustomTest, stringData) { @@ -575,6 +609,45 @@ TEST_F(OptionCustomTest, ipv6AddressDataArray) { ); } +// The purpose of this test is to verify that the option comprising +// an array of FQDN values can be created from a buffer which holds +// multiple FQDN values encoded as described in the RFC1035, section +// 3.1 +TEST_F(OptionCustomTest, fqdnDataArray) { + OptionDefinition opt_def("OPTION_FOO", 1000, "fqdn", true); + + 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, + 7, 101, 120, 97, 109, 112, 108, 101, // "example" + 3, 99, 111, 109, // "com" + 0 + }; + + // Create a buffer that holds two FQDNs. + std::vector buf(data, data + sizeof(data)); + + // Create an option from using a buffer. + boost::scoped_ptr option; + ASSERT_NO_THROW( + option.reset(new OptionCustom(opt_def, Option::V6, buf)); + ); + ASSERT_TRUE(option); + + // We expect that two FQDN values have been extracted + // from a buffer. + ASSERT_EQ(2, option->getDataFieldsNum()); + + // Validate both values. + std::string domain0 = option->readFqdn(0); + EXPECT_EQ("mydomain.example.com.", domain0); + + std::string domain1 = option->readFqdn(1); + EXPECT_EQ("example.com.", domain1); +} + // The purpose of this test is to verify that the option definition comprising // a record of various data fields can be used to create an instance of // custom option. @@ -584,30 +657,46 @@ TEST_F(OptionCustomTest, recordData) { OptionDefinition opt_def("OPTION_FOO", 1000, "record"); ASSERT_NO_THROW(opt_def.addRecordField("uint16")); ASSERT_NO_THROW(opt_def.addRecordField("boolean")); + ASSERT_NO_THROW(opt_def.addRecordField("fqdn")); ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address")); ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address")); ASSERT_NO_THROW(opt_def.addRecordField("string")); + const char fqdn_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, + }; + OptionBuffer buf; // Initialize field 0. writeInt(8712, buf); // Initialize field 1 to 'true' buf.push_back(static_cast(1)); - // Initialize field 2 to IPv4 address. + // Initialize field 2. + buf.insert(buf.end(), fqdn_data, fqdn_data + sizeof(fqdn_data)); + // Initialize field 3 to IPv4 address. writeAddress(IOAddress("192.168.0.1"), buf); - // Initialize field 3 to IPv6 address. + // Initialize field 4 to IPv6 address. writeAddress(IOAddress("2001:db8:1::1"), buf); - // Initialize field 4 to string value. + // Initialize field 5 to string value. writeString("ABCD", buf); boost::scoped_ptr option; + try { + option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())); + } catch (const Exception& ex) { + std::cout << ex.what() << std::endl; + } + ASSERT_NO_THROW( option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())); ); ASSERT_TRUE(option); // We should have 5 data fields. - ASSERT_EQ(5, option->getDataFieldsNum()); + ASSERT_EQ(6, option->getDataFieldsNum()); // Verify value in the field 0. uint16_t value0 = 0; @@ -620,19 +709,24 @@ TEST_F(OptionCustomTest, recordData) { EXPECT_TRUE(value1); // Verify value in the field 2. - IOAddress value2("127.0.0.1"); - ASSERT_NO_THROW(value2 = option->readAddress(2)); - EXPECT_EQ("192.168.0.1", value2.toText()); + std::string value2 = ""; + ASSERT_NO_THROW(value2 = option->readFqdn(2)); + EXPECT_EQ("mydomain.example.com.", value2); // Verify value in the field 3. - IOAddress value3("::1"); + IOAddress value3("127.0.0.1"); ASSERT_NO_THROW(value3 = option->readAddress(3)); - EXPECT_EQ("2001:db8:1::1", value3.toText()); + EXPECT_EQ("192.168.0.1", value3.toText()); // Verify value in the field 4. - std::string value4; - ASSERT_NO_THROW(value4 = option->readString(4)); - EXPECT_EQ("ABCD", value4); + IOAddress value4("::1"); + ASSERT_NO_THROW(value4 = option->readAddress(4)); + EXPECT_EQ("2001:db8:1::1", value4.toText()); + + // Verify value in the field 4. + std::string value5; + ASSERT_NO_THROW(value5 = option->readString(5)); + EXPECT_EQ("ABCD", value5); } // The purpose of this test is to verify that truncated buffer @@ -901,6 +995,4 @@ TEST_F(OptionCustomTest, invalidIndex) { EXPECT_THROW(option->readInteger(11), isc::OutOfRange); } - - } // anonymous namespace diff --git a/src/lib/dhcp/tests/option_data_types_unittest.cc b/src/lib/dhcp/tests/option_data_types_unittest.cc index e18ec243a5..8b8ee487be 100644 --- a/src/lib/dhcp/tests/option_data_types_unittest.cc +++ b/src/lib/dhcp/tests/option_data_types_unittest.cc @@ -52,8 +52,10 @@ TEST_F(OptionDataTypesTest, readFqdn) { // Read the buffer as FQDN and verify its correctness. std::string fqdn; - EXPECT_NO_THROW(fqdn = OptionDataTypeUtil::readFqdn(buf)); + size_t len = 0; + EXPECT_NO_THROW(fqdn = OptionDataTypeUtil::readFqdn(buf, len)); EXPECT_EQ("mydomain.example.com.", fqdn); + EXPECT_EQ(len, buf.size()); // By resizing the buffer we simulate truncation. The first // length field (8) indicate that the first label's size is @@ -61,14 +63,14 @@ TEST_F(OptionDataTypesTest, readFqdn) { // fails. buf.resize(5); EXPECT_THROW( - OptionDataTypeUtil::readFqdn(buf), + OptionDataTypeUtil::readFqdn(buf, len), isc::dhcp::BadDataTypeCast ); // Another special case: provide an empty buffer. buf.clear(); EXPECT_THROW( - OptionDataTypeUtil::readFqdn(buf), + OptionDataTypeUtil::readFqdn(buf, len), isc::dhcp::BadDataTypeCast ); }