Browse Source

[2491] Support for custom option to hold FQDN.

Marcin Siodelski 12 years ago
parent
commit
84e441c756

+ 66 - 25
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;
@@ -269,6 +299,17 @@ OptionCustom::readBoolean(const uint32_t index) const {
 }
 
 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);
     return (OptionDataTypeUtil::readString(buffers_[index]));

+ 11 - 0
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.

+ 5 - 1
src/lib/dhcp/option_data_types.cc

@@ -209,7 +209,10 @@ OptionDataTypeUtil::writeBool(const bool value,
 }
 
 std::string
-OptionDataTypeUtil::readFqdn(const std::vector<uint8_t>& buf) {
+OptionDataTypeUtil::readFqdn(const std::vector<uint8_t>& 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<uint8_t>& 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.

+ 3 - 1
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<uint8_t>& buf);
+    static std::string readFqdn(const std::vector<uint8_t>& buf,
+                                size_t& len);
 
     /// @brief Append FQDN into a buffer.
     ///

+ 106 - 14
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<uint8_t> buf(data, data + sizeof(data));
+
+    boost::scoped_ptr<OptionCustom> 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<uint8_t> buf(data, data + sizeof(data));
+
+    // Create an option from using a buffer.
+    boost::scoped_ptr<OptionCustom> 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<uint16_t>(8712, buf);
     // Initialize field 1 to 'true'
     buf.push_back(static_cast<unsigned short>(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<OptionCustom> 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<uint32_t>(11), isc::OutOfRange);
 }
 
-
-
 } // anonymous namespace

+ 5 - 3
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
     );
 }