Browse Source

[3082] Implemented DHCPv4 Client FQDN Option.

Marcin Siodelski 11 years ago
parent
commit
1ceca44c4f

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

@@ -23,6 +23,7 @@ libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
 libb10_dhcp___la_SOURCES += iface_mgr_sun.cc
 libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
 libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
+libb10_dhcp___la_SOURCES += option4_client_fqdn.cc option4_client_fqdn.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

+ 387 - 0
src/lib/dhcp/option4_client_fqdn.cc

@@ -0,0 +1,387 @@
+// 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/dhcp4.h>
+#include <dhcp/option4_client_fqdn.h>
+#include <dns/labelsequence.h>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+#include <util/strutil.h>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+class Option4ClientFqdnImpl {
+public:
+    uint8_t flags_;
+    Option4ClientFqdn::Rcode rcode1_;
+    Option4ClientFqdn::Rcode rcode2_;
+    boost::shared_ptr<isc::dns::Name> domain_name_;
+    Option4ClientFqdn::DomainNameType domain_name_type_;
+
+    Option4ClientFqdnImpl(const uint8_t flag,
+                          const Option4ClientFqdn::Rcode& rcode,
+                          const std::string& domain_name,
+                          const Option4ClientFqdn::DomainNameType name_type);
+
+    Option4ClientFqdnImpl(OptionBufferConstIter first,
+                          OptionBufferConstIter last);
+
+    Option4ClientFqdnImpl(const Option4ClientFqdnImpl& source);
+
+    Option4ClientFqdnImpl& operator=(const Option4ClientFqdnImpl& source);
+
+    void setDomainName(const std::string& domain_name,
+                       const Option4ClientFqdn::DomainNameType name_type);
+
+    static void checkFlags(const uint8_t flags);
+
+    void parseWireData(OptionBufferConstIter first,
+                       OptionBufferConstIter last);
+
+};
+
+Option4ClientFqdnImpl::
+Option4ClientFqdnImpl(const uint8_t flag,
+                      const Option4ClientFqdn::Rcode& rcode,
+                      const std::string& domain_name,
+                      const Option4ClientFqdn::DomainNameType name_type)
+    : flags_(flag),
+      rcode1_(rcode),
+      rcode2_(rcode),
+      domain_name_(),
+      domain_name_type_(name_type) {
+
+    //  Check if flags are correct.
+    checkFlags(flags_);
+    // Set domain name. It may throw an exception if domain name has wrong
+    // format.
+    setDomainName(domain_name, name_type);
+}
+
+Option4ClientFqdnImpl::Option4ClientFqdnImpl(OptionBufferConstIter first,
+                                             OptionBufferConstIter last)
+    : rcode1_(Option4ClientFqdn::RCODE_CLIENT()),
+      rcode2_(Option4ClientFqdn::RCODE_CLIENT()) {
+    parseWireData(first, last);
+    // Verify that flags value was correct.
+    checkFlags(flags_);
+}
+
+Option4ClientFqdnImpl::
+Option4ClientFqdnImpl(const Option4ClientFqdnImpl& source)
+    : flags_(source.flags_),
+      rcode1_(source.rcode1_),
+      rcode2_(source.rcode2_),
+      domain_name_(),
+      domain_name_type_(source.domain_name_type_) {
+    if (source.domain_name_) {
+        domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+    }
+}
+
+Option4ClientFqdnImpl&
+Option4ClientFqdnImpl::operator=(const Option4ClientFqdnImpl& source) {
+    domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+
+    // Assignment is exception safe.
+    flags_ = source.flags_;
+    rcode1_ = source.rcode1_;
+    rcode2_ = source.rcode2_;
+    domain_name_type_ = source.domain_name_type_;
+
+    return (*this);
+}
+
+void
+Option4ClientFqdnImpl::
+setDomainName(const std::string& domain_name,
+              const Option4ClientFqdn::DomainNameType name_type) {
+    // domain-name must be trimmed. Otherwise, string comprising spaces only
+    // would be treated as a fully qualified name.
+    std::string name = isc::util::str::trim(domain_name);
+    if (name.empty()) {
+        if (name_type == Option4ClientFqdn::FULL) {
+            isc_throw(InvalidOption4ClientFqdnDomainName,
+                      "fully qualified domain-name must not be empty"
+                      << " when setting new domain-name for DHCPv4 Client"
+                      << " FQDN Option");
+        }
+        // The special case when domain-name is empty is marked by setting the
+        // pointer to the domain-name object to NULL.
+        domain_name_.reset();
+
+    } else {
+        try {
+            domain_name_.reset(new isc::dns::Name(name));
+            domain_name_type_ = name_type;
+
+        } catch (const Exception& ex) {
+            isc_throw(InvalidOption4ClientFqdnDomainName,
+                      "invalid domain-name value '"
+                      << domain_name << "' when setting new domain-name for"
+                      << " DHCPv4 Client FQDN Option");
+
+        }
+    }
+}
+
+void
+Option4ClientFqdnImpl::checkFlags(const uint8_t flags) {
+    // The Must Be Zero (MBZ) bits must not be set.
+    if ((flags & ~Option4ClientFqdn::FLAG_MASK) != 0) {
+        isc_throw(InvalidOption4ClientFqdnFlags,
+                  "invalid DHCPv4 Client FQDN Option flags: 0x"
+                  << std::hex << static_cast<int>(flags) << std::dec);
+    }
+
+    // According to RFC 4702, section 2.1. if the N bit is 1, the S bit
+    // MUST be 0. Checking it here.
+    if ((flags & (Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S))
+        == (Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S)) {
+        isc_throw(InvalidOption4ClientFqdnFlags,
+                  "both N and S flag of the DHCPv4 Client FQDN Option are set."
+                  << " According to RFC 4702, if the N bit is 1 the S bit"
+                  << " MUST be 0");
+    }
+}
+
+void
+Option4ClientFqdnImpl::parseWireData(OptionBufferConstIter first,
+                                     OptionBufferConstIter last) {
+
+    // Buffer must comprise at least one byte with the flags.
+    // The domain-name may be empty.
+    if (std::distance(first, last) < Option4ClientFqdn::FIXED_FIELDS_LEN) {
+        isc_throw(OutOfRange, "DHCPv4 Client FQDN Option ("
+                  << DHO_FQDN << ") is truncated");
+    }
+
+    // Parse flags
+    flags_ = *(first++);
+
+    // Parse RCODE1 and RCODE2.
+    rcode1_ = Option4ClientFqdn::Rcode(*(first++));
+    rcode2_ = Option4ClientFqdn::Rcode(*(first++));
+
+    // Parse domain-name if any.
+    if (std::distance(first, last) > 0) {
+        // The FQDN may comprise a partial domain-name. In this case it lacks
+        // terminating 0. If this is the case, we will need to add zero at
+        // the end because Name object constructor requires it.
+        if (*(last - 1) != 0) {
+            // Create temporary buffer and add terminating zero.
+            OptionBuffer buf(first, last);
+            buf.push_back(0);
+            // Reset domain name.
+            isc::util::InputBuffer name_buf(&buf[0], buf.size());
+            domain_name_.reset(new isc::dns::Name(name_buf));
+            // Terminating zero was missing, so set the domain-name type
+            // to partial.
+            domain_name_type_ = Option4ClientFqdn::PARTIAL;
+        } else {
+            // We are dealing with fully qualified domain name so there is
+            // no need to add terminating zero. Simply pass the buffer to
+            // Name object constructor.
+            isc::util::InputBuffer name_buf(&(*first),
+                                            std::distance(first, last));
+            domain_name_.reset(new isc::dns::Name(name_buf));
+            // Set the domain-type to fully qualified domain name.
+            domain_name_type_ = Option4ClientFqdn::FULL;
+        }
+    }
+}
+
+    Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag, const Rcode& rcode)
+    : Option(Option::V4, DHO_FQDN),
+      impl_(new Option4ClientFqdnImpl(flag, rcode, "", PARTIAL)) {
+}
+
+Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag,
+                                     const Rcode& rcode,
+                                     const std::string& domain_name,
+                                     const DomainNameType domain_name_type)
+    : Option(Option::V4, DHO_FQDN),
+      impl_(new Option4ClientFqdnImpl(flag, rcode, domain_name,
+                                      domain_name_type)) {
+}
+
+Option4ClientFqdn::Option4ClientFqdn(OptionBufferConstIter first,
+                                     OptionBufferConstIter last)
+    : Option(Option::V4, DHO_FQDN, first, last),
+      impl_(new Option4ClientFqdnImpl(first, last)) {
+}
+
+Option4ClientFqdn::~Option4ClientFqdn() {
+    delete(impl_);
+}
+
+Option4ClientFqdn::Option4ClientFqdn(const Option4ClientFqdn& source)
+    : Option(source),
+      impl_(new Option4ClientFqdnImpl(*source.impl_)) {
+}
+
+Option4ClientFqdn&
+Option4ClientFqdn::operator=(const Option4ClientFqdn& source) {
+    Option4ClientFqdnImpl* old_impl = impl_;
+    impl_ = new Option4ClientFqdnImpl(*source.impl_);
+    delete(old_impl);
+    return (*this);
+}
+
+bool
+Option4ClientFqdn::getFlag(const Flag flag) const {
+    // Caller should query for one of the: E, N, S or O flags. However, there 
+    // are valid enumerator values which should not be accepted by this function.
+    // For example a 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 && flag != FLAG_E) {
+        isc_throw(InvalidOption4ClientFqdnFlags, "invalid DHCPv4 Client FQDN"
+                  << " Option flag specified, expected E, N, S or O");
+    }
+
+    return ((impl_->flags_ & flag) != 0);
+}
+
+void
+Option4ClientFqdn::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(InvalidOption4ClientFqdnFlags, "invalid DHCPv4 Client FQDN"
+                  << " Option flag " << std::hex
+                  << static_cast<int>(flag) << std::dec
+                  << "is being set. Expected combination of E, 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 = impl_->flags_;
+    if (set_flag) {
+        new_flag |= flag;
+    } else {
+        new_flag &= ~flag;
+    }
+
+    // Check new flags. If they are valid, apply them.
+    Option4ClientFqdnImpl::checkFlags(new_flag);
+    impl_->flags_ = new_flag;
+}
+
+void
+Option4ClientFqdn::setRcode(const Rcode& rcode) {
+    impl_->rcode1_ = rcode;
+    impl_->rcode2_ = rcode;
+}
+
+void
+Option4ClientFqdn::resetFlags() {
+    impl_->flags_ = 0;
+}
+
+std::string
+Option4ClientFqdn::getDomainName() const {
+    if (impl_->domain_name_) {
+        return (impl_->domain_name_->toText(impl_->domain_name_type_ ==
+                                            PARTIAL));
+    }
+    // If an object holding domain-name is NULL it means that the domain-name
+    // is empty.
+    return ("");
+}
+
+void
+Option4ClientFqdn::packDomainName(isc::util::OutputBuffer& buf) const {
+    // Domain name, encoded as a set of labels.
+    isc::dns::LabelSequence labels(*impl_->domain_name_);
+    if (labels.getDataLength() > 0) {
+        size_t read_len = 0;
+        const uint8_t* data = labels.getData(&read_len);
+        if (impl_->domain_name_type_ == PARTIAL) {
+            --read_len;
+        }
+        buf.writeData(data, read_len);
+    }
+}
+
+void
+Option4ClientFqdn::setDomainName(const std::string& domain_name,
+                                 const DomainNameType domain_name_type) {
+    impl_->setDomainName(domain_name, domain_name_type);
+}
+
+void
+Option4ClientFqdn::resetDomainName() {
+    setDomainName("", PARTIAL);
+}
+
+Option4ClientFqdn::DomainNameType
+Option4ClientFqdn::getDomainNameType() const {
+    return (impl_->domain_name_type_);
+}
+
+void
+Option4ClientFqdn::pack(isc::util::OutputBuffer& buf) {
+    // Header = option code and length.
+    packHeader(buf);
+    // Flags field.
+    buf.writeUint8(impl_->flags_);
+    // RCODE1 and RCODE2
+    buf.writeUint8(impl_->rcode1_.getCode());
+    buf.writeUint8(impl_->rcode2_.getCode());
+    // Domain name.
+    packDomainName(buf);
+}
+
+void
+Option4ClientFqdn::unpack(OptionBufferConstIter first,
+                          OptionBufferConstIter last) {
+    setData(first, last);
+    impl_->parseWireData(first, last);
+}
+
+std::string
+Option4ClientFqdn::toText(int indent) {
+    std::ostringstream stream;
+    std::string in(indent, ' '); // base indentation
+    std::string in_add(2, ' ');  // second-level indentation is 2 spaces long
+    stream << in  << "type=" << type_ << "(CLIENT_FQDN)" << std::endl
+           << in << "flags:" << std::endl
+           << in << in_add << "N=" << (getFlag(FLAG_N) ? "1" : "0") << std::endl
+           << in << in_add << "E=" << (getFlag(FLAG_E) ? "1" : "0") << std::endl
+           << in << in_add << "O=" << (getFlag(FLAG_O) ? "1" : "0") << std::endl
+           << in << in_add << "S=" << (getFlag(FLAG_S) ? "1" : "0") << std::endl
+           << in << "domain-name='" << getDomainName() << "' ("
+           << (getDomainNameType() == PARTIAL ? "partial" : "full")
+           << ")" << std::endl;
+
+    return (stream.str());
+}
+
+uint16_t
+Option4ClientFqdn::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 = impl_->domain_name_type_ == FULL ?
+        impl_->domain_name_->getLength() : impl_->domain_name_->getLength() - 1;
+
+    return (getHeaderLen() + FIXED_FIELDS_LEN + domain_name_length);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace

+ 329 - 0
src/lib/dhcp/option4_client_fqdn.h

@@ -0,0 +1,329 @@
+// 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 OPTION4_CLIENT_FQDN_H
+#define OPTION4_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
+/// DHCPv4 Client FQDN %Option.
+class InvalidOption4ClientFqdnFlags : public Exception {
+public:
+    InvalidOption4ClientFqdnFlags(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception thrown when invalid domain name is specified.
+class InvalidOption4ClientFqdnDomainName : public Exception {
+public:
+    InvalidOption4ClientFqdnDomainName(const char* file, size_t line,
+                                 const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// Forward declaration to implementation of @c Option4ClientFqdn class.
+class Option4ClientFqdnImpl;
+
+/// @brief Represents DHCPv4 Client FQDN %Option (code 81).
+///
+/// This option has been defined in the RFC 4702 and it has a following
+/// structure:
+/// - Code (1 octet) - option code (always equal to 81).
+/// - Len (1 octet) - a length of the option.
+/// - Flags (1 octet) - a field carrying "NEOS" flags described below.
+/// - RCODE1 (1 octet) - deprecated field which should be set to 0 by the client
+/// and set to 255 by the server.
+/// - RCODE2 (1 octet) - deprecated, should be used in the same way as RCODE1.
+/// - 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|E|O|S|
+///       +-+-+-+-+-+-+-+-+
+/// @endcode
+/// where:
+/// - N flag specifies whether server should (0) or should not (1) perform DNS
+///  Update,
+/// - E flag indicates encoding of the Domain Name field. If this flag is set to 1
+/// it indicates canonical wire format without compression. 0 indicates the deprecated
+/// ASCII format.
+/// - O flag is set by the server to indicate that it has overridden client's
+/// preference 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 DHCPv4 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. It is also accepted to create an
+/// instance of this option which has empty domain-name. Clients use empty
+/// domain-names to indicate that server should generate complete fully
+/// qualified domain-name.
+///
+/// RFC 4702 mandates that the DHCP client sets RCODE1 and RCODE2 to 0 and that
+/// server sets them to 255. This class allows to set the value for these
+/// fields and both fields are always set to the same value. There is no way
+/// to set them separately (e.g. set different value for RCODE1 and RCODE2).
+/// However, there are no use cases which would require it.
+///
+/// <b>Design choice:</b> This class uses pimpl idiom to separate the interface
+/// from implementation specifics. Implementations may use different approaches
+/// to handle domain names (mostly validation of the domain-names). The existing
+/// @c isc::dns::Name class is a natural (and the simplest) choice to handle
+/// domain-names. Use of this class however, implies that libdhcp must be linked
+/// with libdns. At some point these libraries may need to be separated, i.e. to
+/// support compilation and use of standalone DHCP server. This will require
+/// that the part of implementation which deals with domain-names is modified to
+/// not use classes from libdns. These changes will be transparent for this
+/// interface.
+class Option4ClientFqdn : public Option {
+public:
+
+    /// @brief Enumeration holding different flags used in the Client
+    /// FQDN %Option.
+    enum Flag {
+        FLAG_S = 0x01,
+        FLAG_O = 0x02,
+        FLAG_E = 0x04,
+        FLAG_N = 0x08
+    };
+
+    /// @brief Represents the value of one of the RCODE1 or RCODE2 fields.
+    ///
+    /// Typically, RCODE values are set to 255 by the server and to 0 by the
+    /// clients (as per RFC 4702).
+    class Rcode {
+    public:
+        Rcode(const uint8_t rcode)
+            : rcode_(rcode) { }
+
+        /// @brief Returns the value of the RCODE.
+        ///
+        /// Returned value can be directly used to create the on-wire format
+        /// of the DHCPv4 Client FQDN %Option.
+        uint8_t getCode() const {
+            return (rcode_);
+        }
+
+    private:
+        uint8_t rcode_;
+    };
+
+
+    /// @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 = 0xF;
+
+    /// @brief The size of the fixed fields within DHCPv4 Client Fqdn %Option.
+    ///
+    /// The fixed fields are:
+    /// - Flags
+    /// - RCODE1
+    /// - RCODE2
+    static const uint16_t FIXED_FIELDS_LEN = 3;
+
+    /// @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 rcode @c Rcode object representing a value for RCODE1 and RCODE2
+    /// fields of the option. Both fields are assigned the same value encapsulated
+    /// by the parameter.
+    /// @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 Option4ClientFqdn(const uint8_t flag,
+                               const Rcode& rcode,
+                               const std::string& domain_name,
+                               const DomainNameType domain_name_type = FULL);
+
+    /// @brief Constructor, creates option instance with empty domain name.
+    ///
+    /// This constructor creates an instance of the option with empty
+    /// domain-name. This domain-name is marked partial.
+    ///
+    /// @param flag a combination of flags to be stored in flags field.
+    /// @param rcode @c Rcode object representing a value for RCODE1 and RCODE2
+    /// fields. Both fields are assigned the same value encapsulated by this
+    /// parameter.
+    Option4ClientFqdn(const uint8_t flag, const Rcode& rcode);
+
+    /// @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 parameters 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 Option4ClientFqdn(OptionBufferConstIter first,
+                               OptionBufferConstIter last);
+
+   /// @brief Copy constructor
+    Option4ClientFqdn(const Option4ClientFqdn& source);
+
+    /// @brief Destructor
+    virtual ~Option4ClientFqdn();
+
+    /// @brief Assignment operator
+    Option4ClientFqdn& operator=(const Option4ClientFqdn& source);
+
+    /// @brief Checks if the specified flag of the DHCPv4 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 DHCPv4 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 Sets the flag field value to 0.
+    void resetFlags();
+
+    /// @brief Set Rcode value.
+    ///
+    /// @param rcode An @c Rcode object representing value of RCODE1 and RCODE2.
+    /// Both fields are assigned the same value.
+    void setRcode(const Rcode& rcode);
+
+    /// @brief Returns the domain-name in the text format.
+    ///
+    /// If domain-name is partial, it lacks the dot at the end (e.g. myhost).
+    /// If domain-name is fully qualified, it has the dot at the end (e.g.
+    /// myhost.example.com.).
+    ///
+    /// @return domain-name in the text format.
+    std::string getDomainName() const;
+
+    /// @brief Writes domain-name in the wire format into a buffer.
+    ///
+    /// The data being written are appended at the end of the buffer.
+    ///
+    /// @param [out] buf buffer where domain-name will be written.
+    void packDomainName(isc::util::OutputBuffer& buf) const;
+
+    /// @brief Set new domain-name.
+    ///
+    /// @param domain_name domain name field value in the text format.
+    /// @param domain_name_type type of the domain name: partial or fully
+    /// qualified.
+    void setDomainName(const std::string& domain_name,
+                       const DomainNameType domain_name_type);
+
+    /// @brief Set empty domain-name.
+    ///
+    /// This function is equivalent to @c Option6ClientFqdn::setDomainName
+    /// with empty partial domain-name. It is exception safe.
+    void resetDomainName();
+
+    /// @brief Returns enumerator value which indicates whether domain-name is
+    /// partial or full.
+    ///
+    /// @return An enumerator value indicating whether domain-name is partial
+    /// or full.
+    DomainNameType getDomainNameType() const;
+
+   /// @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 DHCPv4 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 +
+    /// DHCPv4 option header).
+    ///
+    /// @return length of the option.
+    virtual uint16_t len();
+
+    ///
+    /// @name Well known Rcode declarations for DHCPv4 Client FQDN %Option
+    ///
+    //@{
+    /// @brief Rcode being set by the server.
+    inline static const Rcode& RCODE_SERVER() {
+        static Rcode rcode(255);
+        return (rcode);
+    }
+
+    /// @brief Rcode being set by the client.
+    inline static const Rcode& RCODE_CLIENT() {
+        static Rcode rcode(0);
+        return (rcode);
+    }
+    //@}
+
+private:
+
+    /// @brief A pointer to the implementation.
+    Option4ClientFqdnImpl* impl_;
+};
+
+/// A pointer to the @c Option4ClientFqdn object.
+typedef boost::shared_ptr<Option4ClientFqdn> Option4ClientFqdnPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION4_CLIENT_FQDN_H

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

@@ -31,6 +31,7 @@ libdhcp___unittests_SOURCES += hwaddr_unittest.cc
 libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
 libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
 libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
+libdhcp___unittests_SOURCES += option4_client_fqdn_unittest.cc
 libdhcp___unittests_SOURCES += option6_addrlst_unittest.cc
 libdhcp___unittests_SOURCES += option6_ia_unittest.cc
 libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc

+ 756 - 0
src/lib/dhcp/tests/option4_client_fqdn_unittest.cc

@@ -0,0 +1,756 @@
+// 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/option4_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;
+
+// Redefine option flags here as uint8_t. They will be used to initialize
+// elements of the arrays that are used in tests below. Note that use of
+// enum values defined in Option4ClientFqdn class may cause compilation issues
+// during uint8_t arrays initialization. That is because the underlying
+// integral type used to represent enums is larger than one byte.
+const uint8_t FLAG_S = 0x01;
+const uint8_t FLAG_O = 0x02;
+const uint8_t FLAG_E = 0x04;
+const uint8_t FLAG_N = 0x08;
+
+// This test verifies that constructor accepts empty partial domain-name but
+// does not accept empty fully qualified domain name.
+TEST(Option4ClientFqdnTest, constructEmptyName) {
+    // Create an instance of the source option.
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+                                           Option4ClientFqdn::RCODE_SERVER(),
+                                           "",
+                                           Option4ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_TRUE(option->getDomainName().empty());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+
+    // Constructor should not accept empty fully qualified domain name.
+    EXPECT_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+                                   Option4ClientFqdn::RCODE_CLIENT(),
+                                   "",
+                                   Option4ClientFqdn::FULL),
+                 InvalidOption4ClientFqdnDomainName);
+    // This check is similar to previous one, but using domain-name comprising
+    // a single space character. This should be treated as empty domain-name.
+    EXPECT_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+                                   Option4ClientFqdn::RCODE_CLIENT(),
+                                   " ",
+                                   Option4ClientFqdn::FULL),
+                 InvalidOption4ClientFqdnDomainName);
+
+    // Try different constructor.
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O,
+                                           Option4ClientFqdn::RCODE_SERVER()))
+    );
+    ASSERT_TRUE(option);
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_TRUE(option->getDomainName().empty());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that copy constructor makes a copy of the option and
+// the source option instance can be deleted (both instances don't share
+// any resources).
+TEST(Option4ClientFqdnTest, copyConstruct) {
+    // Create an instance of the source option.
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+                                           Option4ClientFqdn::RCODE_SERVER(),
+                                           "myhost.example.com",
+                                           Option4ClientFqdn::FULL))
+    );
+    ASSERT_TRUE(option);
+
+    // Use copy constructor to create a second instance of the option.
+    boost::scoped_ptr<Option4ClientFqdn> option_copy;
+    ASSERT_NO_THROW(
+        option_copy.reset(new Option4ClientFqdn(*option))
+    );
+    ASSERT_TRUE(option_copy);
+
+    // Copy construction should result in no shared resources between
+    // two objects. In particular, pointer to implementation should not
+    // be shared. Thus, we can release the source object now.
+    option.reset();
+
+    // Verify that all parameters have been copied to the target object.
+    EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_EQ("myhost.example.com.", option_copy->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::FULL, option_copy->getDomainNameType());
+
+    // Do another test with different parameters to verify that parameters
+    // change when copied object is changed.
+
+    // Create an option with different parameters.
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O,
+                                           Option4ClientFqdn::RCODE_SERVER(),
+                                           "example",
+                                           Option4ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+
+    // Call copy-constructor to copy the option.
+    ASSERT_NO_THROW(
+        option_copy.reset(new Option4ClientFqdn(*option))
+    );
+    ASSERT_TRUE(option_copy);
+
+    option.reset();
+
+    EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_EQ("example", option_copy->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, option_copy->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWire) {
+    const uint8_t in_data[] = {
+        FLAG_S,                              // flags
+        0,                                   // RCODE1
+        0,                                   // RCODE2
+        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 in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+    OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+    );
+    ASSERT_TRUE(option);
+
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_EQ("myhost.example.com.", option->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that truncated option is rejected.
+TEST(Option4ClientFqdnTest, constructFromWireTruncated) {
+    // Empty buffer is invalid. It should be at least one octet long.
+    OptionBuffer in_buf;
+    for (uint8_t i = 0; i < 3; ++i) {
+        EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()),
+                     OutOfRange) << "Test of the truncated buffer failed for"
+                                 << " buffer length " << static_cast<int>(i);
+        in_buf.push_back(0);
+    }
+
+    // Buffer is now 3 bytes long, so it should not fail now.
+    EXPECT_NO_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()));
+}
+
+// This test verifies that the option in the on-wire format with partial
+// domain-name is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWirePartial) {
+    const uint8_t in_data[] = {
+        FLAG_N,                              // flags
+        255,                                 // RCODE1
+        255,                                 // RCODE2
+        6, 109, 121, 104, 111, 115, 116      // myhost
+    };
+    size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+    OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+    );
+    ASSERT_TRUE(option);
+
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_EQ("myhost", option->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with empty
+// domain-name is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWireEmpty) {
+    // Initialize the 3-byte long buffer. All bytes initialized to 0:
+    // Flags, RCODE1 and RCODE2.
+    OptionBuffer in_buf(3, 0);
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+    );
+    ASSERT_TRUE(option);
+
+    // domain-name field should be empty because on-wire data comprised
+    // flags field only.
+    EXPECT_TRUE(option->getDomainName().empty());
+}
+
+// This test verifies that assignment operator can be used to assign one
+// instance of the option to another.
+TEST(Option4ClientFqdnTest, assignment) {
+    // Usually the smart pointer is used to declare options and call
+    // constructor within assert. Thanks to this approach, the option instance
+    // is in the function scope and only initialization is done within assert.
+    // In this particular test we can't use smart pointers because we are
+    // testing assignment operator like this:
+    //
+    //          option2 = option;
+    //
+    // The two asserts below do not create the instances that we will used to
+    // test assignment. They just attempt to create instances of the options
+    // with the same parameters as those that will be created for the actual
+    // assignment test. If these asserts do not fail, we can create options
+    // for the assignment test, do not surround them with asserts and be sure
+    // they will not throw.
+    ASSERT_NO_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+                                      Option4ClientFqdn::RCODE_SERVER(),
+                                      "myhost.example.com",
+                                      Option4ClientFqdn::FULL));
+
+    ASSERT_NO_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_N,
+                                      Option4ClientFqdn::RCODE_SERVER(),
+                                      "myhost",
+                                      Option4ClientFqdn::PARTIAL));
+
+    // Create options with the same parameters as tested above.
+
+    // Create first option.
+    Option4ClientFqdn option(Option4ClientFqdn::FLAG_S,
+                             Option4ClientFqdn::RCODE_SERVER(),
+                             "myhost.example.com",
+                             Option4ClientFqdn::FULL);
+
+    // Verify that the values have been set correctly.
+    ASSERT_TRUE(option.getFlag(Option4ClientFqdn::FLAG_S));
+    ASSERT_FALSE(option.getFlag(Option4ClientFqdn::FLAG_O));
+    ASSERT_FALSE(option.getFlag(Option4ClientFqdn::FLAG_N));
+    ASSERT_EQ("myhost.example.com.", option.getDomainName());
+    ASSERT_EQ(Option4ClientFqdn::FULL, option.getDomainNameType());
+
+    // Create a second option.
+    Option4ClientFqdn option2(Option4ClientFqdn::FLAG_N,
+                              Option4ClientFqdn::RCODE_SERVER(),
+                              "myhost",
+                              Option4ClientFqdn::PARTIAL);
+
+    // Verify tha the values have been set correctly.
+    ASSERT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_S));
+    ASSERT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O));
+    ASSERT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_N));
+    ASSERT_EQ("myhost", option2.getDomainName());
+    ASSERT_EQ(Option4ClientFqdn::PARTIAL, option2.getDomainNameType());
+
+
+    // Make the assignment.
+    ASSERT_NO_THROW(option2 = option);
+
+    // Both options should now have the same values.
+    EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_EQ(option.getDomainName(), option2.getDomainName());
+    EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType());
+}
+
+// This test verifies that constructor will throw an exception if invalid
+// DHCPv6 Client FQDN Option flags are specified.
+TEST(Option4ClientFqdnTest, constructInvalidFlags) {
+    // 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(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
+                                      "myhost.example.com"));
+
+    // Invalid flags: The maximal value is 0xF when all flag bits are set
+    // (00001111b). The flag value of 0x18 sets the bit from the Must Be
+    // Zero (MBZ) bitset (00011000b).
+    flags = 0x18;
+    EXPECT_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
+                                   "myhost.example.com"),
+                 InvalidOption4ClientFqdnFlags);
+
+    // According to RFC 4702, section 2.1. if the N bit is set the S bit MUST
+    // be zero. If both are set, constructor is expected to throw.
+    flags = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S;
+    EXPECT_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
+                                   "myhost.example.com"),
+                 InvalidOption4ClientFqdnFlags);
+}
+
+// This test verifies that constructor which parses option from on-wire format
+// will throw exception if parsed flags field is invalid.
+TEST(Option4ClientFqdnTest, constructFromWireInvalidFlags) {
+    // Create a buffer which holds flags field only. Set valid flag field at
+    // at first to make sure that constructor doesn't always throw an exception.
+    OptionBuffer in_buf(3, 0);
+    in_buf[0] = FLAG_S;
+    ASSERT_NO_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()));
+
+    // Replace the flags with invalid value and verify that constructor throws
+    // appropriate exception.
+    in_buf[0] = FLAG_N | FLAG_S;
+    EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()),
+                 InvalidOption4ClientFqdnFlags);
+}
+
+// This test verifies that if invalid domain name is used the constructor
+// will throw appropriate exception.
+TEST(Option4ClientFqdnTest, constructInvalidName) {
+    // 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(Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                      "myhost.example.com"));
+
+    // Specify invalid domain name and expect that exception is thrown.
+    EXPECT_THROW(Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                   "my...host.example.com"),
+                 InvalidOption4ClientFqdnDomainName);
+}
+
+// This test verifies that getFlag throws an exception if flag value other
+// than explicitly defined in the Option4ClientFqdn::Flag is spcified.
+TEST(Option4ClientFqdnTest, getFlag) {
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost.example.com"))
+    );
+    ASSERT_TRUE(option);
+
+    // The 0x3 is a valid enumerator value (even though it is not explicitly
+    // included in the Option4ClientFqdn::Flag definition). The getFlag()
+    // function should not accept it. Only explicit values are accepted.
+    EXPECT_THROW(option->getFlag(static_cast<Option4ClientFqdn::Flag>(0x3)),
+                                 InvalidOption4ClientFqdnFlags);
+}
+
+// This test verifies that flags can be modified and that incorrect flags
+// are rejected.
+TEST(Option4ClientFqdnTest, setFlag) {
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost.example.com"))
+    );
+    ASSERT_TRUE(option);
+
+    // All flags should be set to 0 initially.
+    ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+
+    // Set N = 1
+    ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, true));
+    ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N));
+
+    // Set O = 1
+    ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_O, true));
+    ASSERT_TRUE(option->getFlag(Option4ClientFqdn::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(Option4ClientFqdn::FLAG_S, true),
+                 InvalidOption4ClientFqdnFlags);
+
+    // Set N = 0
+    ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, false));
+    ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+
+    // Set S = 1, this should not result in exception because N has been
+    // cleared.
+    ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_S, true));
+    ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+
+    // Set N = 1, this should result in exception because S = 1
+    ASSERT_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, true),
+                 InvalidOption4ClientFqdnFlags);
+
+    // Set O = 0
+    ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_O, false));
+    ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+
+    // Try out of bounds settings.
+    uint8_t flags = 0;
+    ASSERT_THROW(option->setFlag(static_cast<Option4ClientFqdn::Flag>(flags),
+                                 true),
+                 InvalidOption4ClientFqdnFlags);
+
+    flags = 0x14;
+    ASSERT_THROW(option->setFlag(static_cast<Option4ClientFqdn::Flag>(flags),
+                                 true),
+                 InvalidOption4ClientFqdnFlags);
+}
+
+// This test verifies that flags field of the option is set to 0 when resetFlags
+// function is called.
+TEST(Option4ClientFqdnTest, resetFlags) {
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+                                           Option4ClientFqdn::FLAG_O,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost.example.com",
+                                           Option4ClientFqdn::FULL))
+    );
+    ASSERT_TRUE(option);
+
+    // Check that flags we set in the constructor are set.
+    ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+
+    option->resetFlags();
+
+    // After reset, all flags should be 0.
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+}
+
+// This test verifies that current domain-name can be replaced with a new
+// domain-name.
+TEST(Option4ClientFqdnTest, setDomainName) {
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+                                           Option4ClientFqdn::RCODE_SERVER(),
+                                           "myhost.example.com",
+                                           Option4ClientFqdn::FULL))
+    );
+    ASSERT_TRUE(option);
+    ASSERT_EQ("myhost.example.com.", option->getDomainName());
+    ASSERT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+    // Partial domain-name.
+    ASSERT_NO_THROW(option->setDomainName("myhost",
+                                          Option4ClientFqdn::PARTIAL));
+    EXPECT_EQ("myhost", option->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+
+    // Fully qualified domain-name.
+    ASSERT_NO_THROW(option->setDomainName("example.com",
+                                          Option4ClientFqdn::FULL));
+    EXPECT_EQ("example.com.", option->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+    // Empty domain name (partial). This should be successful.
+    ASSERT_NO_THROW(option->setDomainName("", Option4ClientFqdn::PARTIAL));
+    EXPECT_TRUE(option->getDomainName().empty());
+
+    // Fully qualified domain-names must not be empty.
+    EXPECT_THROW(option->setDomainName("", Option4ClientFqdn::FULL),
+                 InvalidOption4ClientFqdnDomainName);
+    EXPECT_THROW(option->setDomainName(" ", Option4ClientFqdn::FULL),
+                 InvalidOption4ClientFqdnDomainName);
+}
+
+// This test verifies that current domain-name can be reset to empty one.
+TEST(Option4ClientFqdnTest, resetDomainName) {
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost.example.com",
+                                           Option4ClientFqdn::FULL))
+    );
+    ASSERT_TRUE(option);
+    ASSERT_EQ("myhost.example.com.", option->getDomainName());
+    ASSERT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+    // Set the domain-name to empty one.
+    ASSERT_NO_THROW(option->resetDomainName());
+    EXPECT_TRUE(option->getDomainName().empty());
+}
+
+// This test verifies on-wire format of the option is correctly created.
+TEST(Option4ClientFqdnTest, pack) {
+    // Create option instance. Check that constructor doesn't throw.
+    const uint8_t flags = Option4ClientFqdn::FLAG_S;
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(flags,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "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[] = {
+        81, 23,                              // header
+        FLAG_S,                              // flags
+        0,                                   // RCODE1
+        0,                                   // RCODE2
+        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(Option4ClientFqdnTest, packPartial) {
+    // Create option instance. Check that constructor doesn't throw.
+    const uint8_t flags = Option4ClientFqdn::FLAG_S;
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(flags,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost",
+                                           Option4ClientFqdn::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[] = {
+        81, 10,                              // header
+        FLAG_S,                              // flags
+        0,                                   // RCODE1
+        0,                                   // RCODE2
+        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 on-wire option data holding fully qualified domain
+// name is parsed correctly.
+TEST(Option4ClientFqdnTest, unpack) {
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost",
+                                           Option4ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+    // Make sure that the parameters have been set correctly. Later in this
+    // test we will check that they will be replaced with new values when
+    // unpack is called.
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_EQ("myhost", option->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+
+    const uint8_t in_data[] = {
+        FLAG_S,                              // flags
+        0,                                   // RCODE1
+        0,                                   // RCODE2
+        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 in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+    OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+    // Initialize new values from the on-wire format.
+    ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end()));
+
+    // Check that new values are correct.
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_EQ("myhost.example.com.", option->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that on-wire option data holding partial domain name
+// is parsed correctly.
+TEST(Option4ClientFqdnTest, unpackPartial) {
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost.example.com"))
+    );
+    ASSERT_TRUE(option);
+    // Make sure that the parameters have been set correctly. Later in this
+    // test we will check that they will be replaced with new values when
+    // unpack is called.
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_EQ("myhost.example.com.", option->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+    const uint8_t in_data[] = {
+        FLAG_S,                              // flags
+        0,                                   // RCODE1
+        0,                                   // RCODE2
+        6, 109, 121, 104, 111, 115, 116      // myhost
+    };
+    size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+    OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+    // Initialize new values from the on-wire format.
+    ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end()));
+
+    // Check that new values are correct.
+    EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+    EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+    EXPECT_EQ("myhost", option->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the empty buffer is rejected when decoding an option
+// from on-wire format.
+TEST(Option4ClientFqdnTest, unpackTruncated) {
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O,
+                                           Option4ClientFqdn::RCODE_CLIENT()))
+    );
+    ASSERT_TRUE(option);
+
+    // Empty buffer is invalid. It should be at least 1 octet long.
+    OptionBuffer in_buf;
+    EXPECT_THROW(option->unpack(in_buf.begin(), in_buf.end()), OutOfRange);
+}
+
+// This test verifies that string representation of the option returned by
+// toText method is correctly formatted.
+TEST(Option4ClientFqdnTest, toText) {
+    // Create option instance. Check that constructor doesn't throw.
+    uint8_t flags = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_O;
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(flags,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost.example.com"))
+    );
+    ASSERT_TRUE(option);
+
+    // The base indentation of the option will be set to 2. It should appear
+    // as follows.
+    std::string ref_string =
+        "  type=81(CLIENT_FQDN)\n"
+        "  flags:\n"
+        "    N=1\n"
+        "    E=0\n"
+        "    O=1\n"
+        "    S=0\n"
+        "  domain-name='myhost.example.com.' (full)\n";
+    const int indent = 2;
+    EXPECT_EQ(ref_string, option->toText(indent));
+
+    // Create another option with different parameters:
+    // - flags set to 0
+    // - domain-name is now partial, not fully qualified
+    // Also, remove base indentation.
+    flags = 0;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(flags,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost",
+                                           Option4ClientFqdn::PARTIAL))
+    );
+    ref_string =
+        "type=81(CLIENT_FQDN)\n"
+        "flags:\n"
+        "  N=0\n"
+        "  E=0\n"
+        "  O=0\n"
+        "  S=0\n"
+        "domain-name='myhost' (partial)\n";
+    EXPECT_EQ(ref_string, option->toText());
+}
+
+// This test verifies that the correct length of the option in on-wire
+// format is returned.
+TEST(Option4ClientFqdnTest, len) {
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost.example.com"))
+    );
+    ASSERT_TRUE(option);
+    // This option comprises a header (2 octets), flag field (1 octet),
+    // RCODE1 and RCODE2 (2 octets) 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 Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                           "example.com"))
+    );
+    ASSERT_TRUE(option);
+    EXPECT_EQ(18, option->len());
+
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost",
+                                           Option4ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+    EXPECT_EQ(12, option->len());
+    }
+
+} // anonymous namespace