Browse Source

[3036] Initial version of DHCPv6 Client FQDN Option class.

Marcin Siodelski 11 years ago
parent
commit
95701a2c5b

+ 1 - 0
src/lib/dhcp/Makefile.am

@@ -26,6 +26,7 @@ libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
 libb10_dhcp___la_SOURCES += option6_ia.cc option6_ia.h
 libb10_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
 libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
+libb10_dhcp___la_SOURCES += option6_client_fqdn.cc option6_client_fqdn.h
 libb10_dhcp___la_SOURCES += option_int.h
 libb10_dhcp___la_SOURCES += option_int_array.h
 libb10_dhcp___la_SOURCES += option.cc option.h

+ 153 - 0
src/lib/dhcp/option6_client_fqdn.cc

@@ -0,0 +1,153 @@
+// Copyright (C) 2013 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 <dhcp/dhcp6.h>
+#include <dhcp/option6_client_fqdn.h>
+#include <dns/labelsequence.h>
+
+namespace isc {
+namespace dhcp {
+
+Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag,
+                                     const std::string& domain_name,
+                                     const DomainNameType domain_name_type)
+    : Option(Option::V6, D6O_CLIENT_FQDN),
+      flags_(flag),
+      domain_name_(NULL),
+      domain_name_type_(domain_name_type) {
+    //  Check if flags are correct.
+    checkFlags(flags_);
+
+    try {
+        domain_name_ = new isc::dns::Name(domain_name);
+
+    } catch (const Exception& ex) {
+        isc_throw(InvalidFqdnOptionDomainName, "invalid domain-name value: "
+                  << domain_name);
+
+    }
+}
+
+Option6ClientFqdn::Option6ClientFqdn(OptionBufferConstIter first,
+                                     OptionBufferConstIter last)
+    : Option(Option::V6, D6O_CLIENT_FQDN, first, last),
+      domain_name_(NULL) {
+}
+
+Option6ClientFqdn::~Option6ClientFqdn() {
+    delete (domain_name_);
+}
+
+bool
+Option6ClientFqdn::getFlag(const Flag flag) const {
+    // Caller should query for one of the: N, S or O flags. However, enumerator
+    // value of 0x3 is valid (because it belongs to the range between the
+    // lowest and highest enumerator). The value 0x3 represents two flags:
+    // S and O and would cause ambiguity. Therefore, we selectively check
+    // that the flag is equal to one of the explicit enumerator values. If
+    // not, throw an exception.
+    if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N) {
+        isc_throw(InvalidFqdnOptionFlags, "invalid DHCPv6 Client FQDN"
+                  << " Option flag specified, expected N, S or O");
+    }
+
+    return ((flags_ & flag) != 0);
+}
+
+void
+Option6ClientFqdn::setFlag(const Flag flag, const bool set_flag) {
+    // Check that flag is in range between 0x1 and 0x7. Note that this
+    // allows to set or clear multiple flags concurrently.
+    if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) {
+        isc_throw(InvalidFqdnOptionFlags, "invalid DHCPv6 Client FQDN"
+                  << " Option flag " << std::hex
+                  << static_cast<int>(flag) << std::dec
+                  << "is being set. Expected combination of N, S and O");
+    }
+
+    // Copy the current flags into local variable. That way we will be able
+    // to test new flags settings before applying them.
+    uint8_t new_flag = flags_;
+    if (set_flag) {
+        new_flag |= flag;
+    } else {
+        new_flag &= ~flag;
+    }
+
+    // Check new flags. If they are valid, apply them.
+    checkFlags(new_flag);
+    flags_ = new_flag;
+}
+
+void
+Option6ClientFqdn::pack(isc::util::OutputBuffer& buf) {
+    // Header = option code and length.
+    packHeader(buf);
+    // Flags field.
+    buf.writeUint8(flags_);
+    // Domain name, encoded as a set of labels.
+    isc::dns::LabelSequence labels(*domain_name_);
+    if (labels.getDataLength() > 0) {
+        size_t read_len = 0;
+        const uint8_t* data = labels.getData(&read_len);
+        if (domain_name_type_ == PARTIAL) {
+            --read_len;
+        }
+        buf.writeData(data, read_len);
+    }
+
+
+}
+
+void
+Option6ClientFqdn::unpack(OptionBufferConstIter,
+                          OptionBufferConstIter) {
+}
+
+std::string
+Option6ClientFqdn::toText(int) {
+    return std::string();
+}
+
+uint16_t
+Option6ClientFqdn::len() {
+    // If domain name is partial, the NULL terminating character
+    // is not included and the option length have to be adjusted.
+    uint16_t domain_name_length = domain_name_type_ == FULL ?
+        domain_name_->getLength() : domain_name_->getLength() - 1;
+
+    return (getHeaderLen() + FLAG_FIELD_LEN + domain_name_length);
+}
+
+void
+Option6ClientFqdn::checkFlags(const uint8_t flags) {
+    // The Must Be Zero (MBZ) bits must not be set.
+    if ((flags & ~FLAG_MASK) != 0) {
+        isc_throw(InvalidFqdnOptionFlags,
+                  "invalid DHCPv6 Client FQDN Option flags: 0x"
+                  << std::hex << static_cast<int>(flags) << std::dec);
+    }
+
+    // According to RFC 4704, section 4.1. if the N bit is 1, the S bit
+    // MUST be 0. Checking it here.
+    if ((flags & (FLAG_N | FLAG_S)) == (FLAG_N | FLAG_S)) {
+        isc_throw(InvalidFqdnOptionFlags,
+                  "both N and S flag of the DHCPv6 Client FQDN Option are set."
+                  << " According to RFC 4704, if the N bit is 1 the S bit"
+                  << " MUST be 0");
+    }
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace

+ 201 - 0
src/lib/dhcp/option6_client_fqdn.h

@@ -0,0 +1,201 @@
+// Copyright (C) 2013 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 OPTION6_CLIENT_FQDN_H
+#define OPTION6_CLIENT_FQDN_H
+
+#include <dhcp/option.h>
+#include <dns/name.h>
+
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when invalid flags have been specified for
+/// DHCPv6 Client Fqdn %Option.
+class InvalidFqdnOptionFlags : public Exception {
+public:
+    InvalidFqdnOptionFlags(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception thrown when invalid domain name is specified.
+class InvalidFqdnOptionDomainName : public Exception {
+public:
+    InvalidFqdnOptionDomainName(const char* file, size_t line,
+                                const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+
+/// @brief Represents DHCPv6 Client FQDN %Option (code 39).
+///
+/// This option has been defined in the RFC 4704 and it has a following
+/// structure:
+/// - option-code = 39 (2 octets)
+/// - option-len (2 octets)
+/// - flags (1 octet)
+/// - domain-name - variable length field comprising partial or fully qualified
+/// domain name.
+///
+/// The flags field has the following structure:
+/// @code
+///        0 1 2 3 4 5 6 7
+///       +-+-+-+-+-+-+-+-+
+///       |  MBZ    |N|O|S|
+///       +-+-+-+-+-+-+-+-+
+/// @endcode
+/// where:
+/// - N flag specifies whether server should (0) or should not (1) perform DNS
+///  Update,
+/// - O flag is set by the server to indicate that it has overriden client's
+/// preferrence set with the S bit.
+/// - S flag specifies whether server should (1) or should not (0) perform
+/// forward (FQDN-to-address) updates.
+///
+/// This class exposes a set of functions to modify flags and check their
+/// correctness.
+///
+/// Domain names being carried by DHCPv6 Client Fqdn %Option can be fully
+/// qualified or partial. Partial domain names are encoded similar to the
+/// fully qualified domain names, except that they lack terminating zero
+/// at the end of their wire representation.
+class Option6ClientFqdn : public Option {
+public:
+
+    /// @brief Enumeration holding different flags used in the Client
+    /// FQDN %Option.
+    enum Flag {
+        FLAG_S = 0x01,
+        FLAG_O = 0x02,
+        FLAG_N = 0x04
+    };
+
+    /// @brief Type of the domain-name: partial or full.
+    enum DomainNameType {
+        PARTIAL,
+        FULL
+    };
+
+    /// @brief Mask which zeroes MBZ flag bits.
+    static const uint8_t FLAG_MASK = 0x7;
+
+    /// @brief The length of the flag field within DHCPv6 Client Fqdn %Option.
+    static const uint16_t FLAG_FIELD_LEN = 1;
+
+    /// @brief Constructor, creates option instance using flags and domain name.
+    ///
+    /// This constructor is used to create instance of the option which will be
+    /// included in outgoing messages.
+    ///
+    /// @param flag a combination of flags to be stored in flags field.
+    /// @param domain_name a name to be stored in the domain-name field.
+    /// @param partial_domain_name indicates if the domain name is partial
+    /// (if true) or full (false).
+    explicit Option6ClientFqdn(const uint8_t flag,
+                               const std::string& domain_name,
+                               const DomainNameType domain_name_type = FULL);
+
+    /// @brief Constructor, creates an option instance from part of the buffer.
+    ///
+    /// This constructor is mainly used to parse options in the received
+    /// messages. Function parameters specify buffer bounds from which the
+    /// option should be created. The size of the buffer chunk, specified by
+    /// the constructor's paramaters should be equal or larger than the size
+    /// of the option. Otherwise, constructor will throw an exception.
+    ///
+    /// @param first the lower bound of the buffer to create option from.
+    /// @param last the upper bound of the buffer to create option from.
+    explicit Option6ClientFqdn(OptionBufferConstIter first,
+                               OptionBufferConstIter last);
+
+    /// @brief Destructor
+    virtual ~Option6ClientFqdn();
+
+    /// @brief Checks if the specified flag of the DHCPv6 Client FQDN %Option
+    /// is set.
+    ///
+    /// @param flag an enum value specifying the flag to be checked.
+    ///
+    /// @return true if the bit of the specified flag is set, false otherwise.
+    bool getFlag(const Flag flag) const;
+
+    /// @brief Modifies the value of the specified DHCPv6 Client Fqdn %Option
+    /// flag.
+    ///
+    /// @param flag an enum value specifying the flag to be modified.
+    /// @param set a boolean value which indicates whether flag should be
+    /// set (true), or cleared (false).
+    void setFlag(const Flag flag, const bool set);
+
+   /// @brief Writes option in the wire format into a buffer.
+    ///
+    /// @param [out] buf output buffer where option data will be stored.
+    virtual void pack(isc::util::OutputBuffer& buf);
+
+    /// @brief Parses option from the received buffer.
+    ///
+    /// Method creates an instance of the DHCPv6 Client FQDN %Option from the
+    /// wire format. Parameters specify the bounds of the buffer to read option
+    /// data from. The size of the buffer limited by the specified parameters
+    /// should be equal or larger than size of the option (including its
+    /// header). Otherwise exception will be thrown.
+    ///
+    /// @param first lower bound of the buffer to parse option from.
+    /// @param last upper bound of the buffer to parse option from.
+    virtual void unpack(OptionBufferConstIter first,
+                        OptionBufferConstIter last);
+
+    /// @brief Returns string representation of the option.
+    ///
+    /// The string returned by the method comprises the bit value of each
+    /// option flag and the domain-name.
+    ///
+    /// @param indent number of spaces before printed text.
+    ///
+    /// @return string with text representation.
+    virtual std::string toText(int indent = 0);
+
+    /// @brief Returns length of the complete option (data length +
+    /// DHCPv6 option header).
+    ///
+    /// @return length of the option.
+    virtual uint16_t len();
+
+private:
+
+    /// @brief Verifies that flags are correct.
+    ///
+    /// Function throws @c isc::dhcp::InvalidFqdnOptionFlags exception if
+    /// current setting of DHCPv6 Client Fqdn %Option flags is invalid.
+    /// In particular, it checks that if N is set, S is cleared.
+    ///
+    /// @param flags DHCPv6 Client FQDN %Option flags to be checked.
+    ///
+    /// @throw isc::dhcp::InvalidFqdnOptionFlags if flags are incorrect.
+    static void checkFlags(const uint8_t flags);
+
+    uint8_t flags_;
+    dns::Name* domain_name_;
+    DomainNameType domain_name_type_;
+};
+
+/// A pointer to the @c Option6ClientFqdn object.
+typedef boost::shared_ptr<Option6ClientFqdn> Option6ClientFqdnPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION6_CLIENT_FQDN_H

+ 1 - 0
src/lib/dhcp/tests/Makefile.am

@@ -32,6 +32,7 @@ libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
 libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
 libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
 libdhcp___unittests_SOURCES += option6_addrlst_unittest.cc
+libdhcp___unittests_SOURCES += option6_client_fqdn_unittest.cc
 libdhcp___unittests_SOURCES += option6_ia_unittest.cc
 libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
 libdhcp___unittests_SOURCES += option_int_unittest.cc

+ 235 - 0
src/lib/dhcp/tests/option6_client_fqdn_unittest.cc

@@ -0,0 +1,235 @@
+// Copyright (C) 2013 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 <config.h>
+#include <dhcp/option6_client_fqdn.h>
+#include <dns/name.h>
+#include <util/buffer.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+namespace {
+
+using namespace isc;
+using namespace isc::dhcp;
+
+class Option6ClientFqdnTest : public ::testing::Test {
+public:
+
+    Option6ClientFqdnTest() { }
+
+    virtual ~Option6ClientFqdnTest() { }
+};
+
+TEST_F(Option6ClientFqdnTest, ctorInvalidFlags) {
+    // First, check that constructor does not throw an exception when
+    // valid flags values are provided. That way we eliminate the issue
+    // that constructor always throws exception.
+    uint8_t flags = 0;
+    ASSERT_NO_THROW(Option6ClientFqdn(flags, "myhost.example.com"));
+
+    // Invalid flags: The maximal value is 0x7 when all flag bits are set
+    // (00000111b). The flag value of 0x14 sets the bit from the Must Be
+    // Zero (MBZ) bitset (00001100b).
+    flags = 0x14;
+    EXPECT_THROW(Option6ClientFqdn(flags, "myhost.example.com"),
+                 InvalidFqdnOptionFlags);
+
+    // According to RFC 4704, section 4.1. if the N bit is set the S bit MUST
+    // be zero. If both are set, constructor is expected to throw.
+    flags = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S;
+    EXPECT_THROW(Option6ClientFqdn(flags, "myhost.example.com"),
+                 InvalidFqdnOptionFlags);
+}
+
+// This test verifies that if invalid domain name is used the constructor
+// will throw appropriate exception.
+TEST_F(Option6ClientFqdnTest, ctorInvalidName) {
+    // First, check that constructor does not throw when valid domain name
+    // is specified. That way we eliminate the possibility that constructor
+    // always throws exception.
+    ASSERT_NO_THROW(Option6ClientFqdn(0, "myhost.example.com"));
+
+    // Specify invalid domain name and expect that exception is thrown.
+    EXPECT_THROW(Option6ClientFqdn(0, "my...host.example.com"),
+                 InvalidFqdnOptionDomainName);
+}
+
+// This test verifies that getFlag throws an exception if flag value of 0x3
+// is specified.TThis test does not verify other invalid values, e.g. 0x5,
+// 0x6 etc. because conversion of int values which do not belong to the range
+// between the lowest and highest enumerator will give an undefined
+// result.
+TEST_F(Option6ClientFqdnTest, getFlag) {
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
+    );
+    ASSERT_TRUE(option);
+
+    // The 0x3 is a valid enumerator value (even though it is not explicitly
+    // included in the Option6ClientFqdn::Flag definition). The getFlag()
+    // function should not accept it. Only explicit values are accepted.
+    EXPECT_THROW(option->getFlag(static_cast<Option6ClientFqdn::Flag>(0x3)),
+                                 InvalidFqdnOptionFlags);
+}
+
+// This test verifies that flags can be modified and that incorrect flags
+// are rejected.
+TEST_F(Option6ClientFqdnTest, setFlag) {
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
+    );
+    ASSERT_TRUE(option);
+
+    // All flags should be set to 0 initially.
+    ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+    ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+    ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+
+    // Set N = 1
+    ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, true));
+    ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_N));
+
+    // Set O = 1
+    ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_O, true));
+    ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+
+    // Set S = 1, this should throw exception because S and N must not
+    // be set in the same time.
+    ASSERT_THROW(option->setFlag(Option6ClientFqdn::FLAG_S, true),
+                 InvalidFqdnOptionFlags);
+
+    // Set N = 0
+    ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, false));
+    ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+
+    // Set S = 1, this should not result in exception because N has been
+    // cleared.
+    ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_S, true));
+    ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+
+    // Set N = 1, this should result in exception because S = 1
+    ASSERT_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, true),
+                 InvalidFqdnOptionFlags);
+
+    // Set O = 0
+    ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_O, false));
+    ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+
+    // Try out of bounds settings.
+    uint8_t flags = 0;
+    ASSERT_THROW(option->setFlag(static_cast<Option6ClientFqdn::Flag>(flags),
+                                 true),
+                 InvalidFqdnOptionFlags);
+
+    flags = 0x14;
+    ASSERT_THROW(option->setFlag(static_cast<Option6ClientFqdn::Flag>(flags),
+                                 true),
+                 InvalidFqdnOptionFlags);
+}
+
+// This test verifies on-wire format of the option is correctly created.
+TEST_F(Option6ClientFqdnTest, pack) {
+    // Create option instance. Check that constructor doesn't throw.
+    const uint8_t flags = Option6ClientFqdn::FLAG_S;
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(flags, "myhost.example.com"))
+    );
+    ASSERT_TRUE(option);
+
+    // Prepare on-wire format of the option.
+    isc::util::OutputBuffer buf(10);
+    ASSERT_NO_THROW(option->pack(buf));
+
+    // Prepare reference data.
+    const uint8_t ref_data[] = {
+        0, 39, 0, 21,                        // header
+        Option6ClientFqdn::FLAG_S,           // flags
+        6, 109, 121, 104, 111, 115, 116,     // myhost.
+        7, 101, 120, 97, 109, 112, 108, 101, // example.
+        3, 99, 111, 109, 0                   // com.
+    };
+    size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+    // Check if the buffer has the same length as the reference data,
+    // so as they can be compared directly.
+    ASSERT_EQ(ref_data_size, buf.getLength());
+    EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies on-wire format of the option with partial domain name
+// is correctly created.
+TEST_F(Option6ClientFqdnTest, packPartial) {
+    // Create option instance. Check that constructor doesn't throw.
+    const uint8_t flags = Option6ClientFqdn::FLAG_S;
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(flags, "myhost",
+                                           Option6ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+
+    // Prepare on-wire format of the option.
+    isc::util::OutputBuffer buf(10);
+    ASSERT_NO_THROW(option->pack(buf));
+
+    // Prepare reference data.
+    const uint8_t ref_data[] = {
+        0, 39, 0, 8,                         // header
+        Option6ClientFqdn::FLAG_S,           // flags
+        6, 109, 121, 104, 111, 115, 116      // myhost
+    };
+    size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+    // Check if the buffer has the same length as the reference data,
+    // so as they can be compared directly.
+    ASSERT_EQ(ref_data_size, buf.getLength());
+    EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies that the correct length of the option in on-wire
+// format is returned.
+TEST_F(Option6ClientFqdnTest, len) {
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
+    );
+    ASSERT_TRUE(option);
+    // This option comprises a header (4 octets), flag field (1 octet),
+    // and wire representation of the domain name (length equal to the
+    // length of the string representation of the domain name + 1).
+    EXPECT_EQ(25, option->len());
+
+    // Let's check that the size will change when domain name of a different
+    // size is used.
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(0, "example.com"))
+    );
+    ASSERT_TRUE(option);
+    EXPECT_EQ(18, option->len());
+
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(0, "myhost",
+                                           Option6ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+    EXPECT_EQ(12, option->len());
+}
+
+} // anonymous namespace