Browse Source

[master] Merge branch 'trac3036'

Conflicts:
	src/lib/dhcp/option_definition.cc
	src/lib/dhcp/option_definition.h
Marcin Siodelski 11 years ago
parent
commit
209f3964b9

+ 1 - 0
doc/devel/mainpage.dox

@@ -56,6 +56,7 @@
  *   - @subpage dhcpv6Session
  *   - @subpage dhcpv6ConfigParser
  *   - @subpage dhcpv6ConfigInherit
+ *   - @subpage dhcpv6DDNSIntegration
  *   - @subpage dhcpv6Other
  * - @subpage libdhcp
  *   - @subpage libdhcpIntro

+ 3 - 0
doc/guide/bind10-guide.xml

@@ -5268,6 +5268,9 @@ should include options from the isc option space:
           <listitem>
             <simpara><ulink url="http://tools.ietf.org/html/rfc3646">RFC 3646</ulink>: Supported option is DNS_SERVERS.</simpara>
           </listitem>
+          <listitem>
+            <simpara><ulink url="http://tools.ietf.org/html/rfc4704">RFC 4704</ulink>: Supported option is CLIENT_FQDN.</simpara>
+          </listitem>
       </itemizedlist>
     </section>
 

+ 1 - 0
src/bin/dhcp6/Makefile.am

@@ -61,6 +61,7 @@ b10_dhcp6_LDADD  = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la

+ 76 - 1
src/bin/dhcp6/dhcp6.dox

@@ -1,4 +1,4 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-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
@@ -90,6 +90,81 @@
  simple as possible. In fact, currently the code has to call Subnet6->getT1() and
  do not implement any fancy inheritance logic.
 
+@section dhcpv6DDNSIntegration DHCPv6 Server Support for the Dynamic DNS Updates
+
+The DHCPv6 server supports processing of the DHCPv6 Client FQDN Option described in
+the RFC4704. This Option is sent by the DHCPv6 client to instruct the server to
+update the DNS mappings for the acquired lease. A client may send its fully
+qualified domain name, a partial name or it may choose that server will generate
+the name. In the last case, the client sends an empty domain-name field in the
+DHCPv6 Client FQDN Option.
+
+As described in RFC4704, client may choose that the server delegates the forward
+DNS update to the client and that the server performs the reverse update only. Current
+version of the DHCPv6 server does not support delegation of the forward update
+to the client. The implementation of this feature is planned for the future releases.
+
+The b10-dhcp-ddns process is responsible for the actual communication with the DNS
+server, i.e. to send DNS Update messages. The b10-dhcp6 module is responsible
+for generating so called @ref isc::dhcp_ddns::NameChangeRequest and sending it to the
+b10-dhcp-ddns module. The @ref isc::dhcp_ddns::NameChangeRequest object represents changes to the
+DNS bindings, related to acquisition, renewal or release of the lease. The bind10-dhcp6
+module implements the simple FIFO queue of the NameChangeRequest objects. The module
+logic, which processes the incoming DHCPv6 Client FQDN Options puts these requests
+into the FIFO queue.
+
+@todo Currently the FIFO queue is not processed after the NameChangeRequests are
+generated and added to it. In the future implementation steps it is planned to create
+a code which will check if there are any outstanding requests in the queue and
+send them to the bind10-dhcp-ddns module when server is idle waiting for DHCP messages.
+
+In the simplest case, when client gets one address from the server, a DHCPv6 server
+may generate 0, 1 or 2 NameChangeRequests during single message processing. 
+Server generates no NameChangeRequests if it is not configured to update DNS
+ or it rejects the DNS update for any other reason.
+
+Server may generate 1 NameChangeRequests in a situation when a client acquires a
+new lease or it releases an existing lease. In the former case, the NameChangeRequest
+type is CHG_ADD, which indicates that the bind10-dhcp-ddns module should add a new DNS
+binding for the client, and it is assumed that there is no DNS binding for this
+client already. In the latter case, the NameChangeRequest type is CHG_REMOVE to
+indicate to the bind10-dhcp-ddns module that the existing DNS binding should be removed
+from the DNS. The binding consists of the forward and reverse mapping.
+A server may only remove the mapping which it had added. Therefore, the lease database
+holds an information which updates (no update, reverse only update, forward only update,
+both reverse and forward update) have been performed when the lease was acquired.
+Server checks this information to make a decision which mapping it is supposed to
+remove when a lease is released.
+
+Server may generate 2 NameChangeRequests in case the client is renewing a lease and
+it already has a DNS binding for that lease. Note, that renewal may be triggered
+as a result of sending a RENEW message as well as the REQUEST message. In both cases
+DHCPv6 server will check if there is an existing lease for the client which has sent
+a message, and if there is it will check in the lease database if the DNS Updates had
+been performed for this client. If the notion of client's FQDN changes comparing to
+the information stored in the lease database, the DHCPv6 has to remove an existing
+binding from the DNS and then add a new binding according to the new FQDN information
+received from the client. If the FQDN sent in the message which triggered a renewal
+doesn't change (comparing to the information in the lease database) the NameChangeRequest
+is not generated.
+
+In the more complex scenarios, when server sends multiple IA_NA options, each holding
+multiple IAADDR options, server will generate more NameChangeRequests for a single
+message being processed. That is 0, 1, 2 for the individual IA_NA. Generation of
+the distinct NameChangeRequests for each IADDR is not supported yet.
+
+The DHCPv6 Client FQDN Option comprises "NOS" flags which communicate to the
+server what updates (if any), client expects the server to perform. Server
+may be configured to obey client's preference or do FQDN processing in a
+different way. If the server overrides client's preference it will communicate it
+by sending the DHCPv6 Client FQDN Option in its responses to a client, with
+appropriate flags set.
+
+@todo Note, that current implementation doesn't allow configuration of the server's behaviour
+with respect to DNS Updates. This is planned for the future. The default behaviour is
+constituted by the set of constants defined in the (upper part of) dhcp6_srv.cc file.
+Once the configuration is implemented, these constants will be removed.
+
  @todo Add section about setting up options and their definitions with bindctl.
 
  @section dhcpv6Other Other DHCPv6 topics

+ 37 - 0
src/bin/dhcp6/dhcp6_messages.mes

@@ -70,11 +70,48 @@ This informational message is printed every time the IPv6 DHCP server
 is started.  It indicates what database backend type is being to store
 lease and other information.
 
+% DHCP6_DDNS_CREATE_ADD_NAME_CHANGE_REQUEST created name change request: %1
+This debug message is logged when the new Name Change Request has been created
+to perform the DNS Update, which adds new RRs.
+
+% DHCP6_DDNS_CREATE_REMOVE_NAME_CHANGE_REQUEST created name change request: %1
+This debug message is logged when the new Name Change Request has been created
+to perform the DNS Update, which removes RRs from the DNS.
+
+% DHCP6_DDNS_LEASE_ASSIGN_FQDN_CHANGE FQDN for the allocated lease: %1 has changed. New values: hostname = %2, reverse mapping = %3, forward mapping = %4
+This debug message is logged when FQDN mapping for a particular lease has
+been changed by the recent Request message. This mapping will be changed in DNS.
+
+% DHCP6_DDNS_LEASE_RENEW_FQDN_CHANGE FQDN for the renewed lease: %1 has changed.
+New  values: hostname = %2, reverse mapping = %3, forward mapping = %4
+This debug message is logged when FQDN mapping for a particular lease has been
+changed by the recent Renew message. This mapping will be changed in DNS.
+
 % DHCP6_DEACTIVATE_INTERFACE deactivate interface %1
 This message is printed when DHCPv6 server disables an interface from being
 used to receive DHCPv6 traffic. Sockets on this interface will not be opened
 by the Interface Manager until interface is enabled.
 
+% DHCP6_DDNS_SEND_FQDN sending DHCPv6 Client FQDN Option to the client: %1
+This debug message is logged when server includes an DHCPv6 Client FQDN Option
+in its response to the client.
+
+% DHCP6_DDNS_RECEIVE_FQDN received DHCPv6 Client FQDN Option: %1
+This debug message is logged when server has found the DHCPv6 Client FQDN Option
+sent by a client and started processing it.
+
+% DHCP6_DDNS_REMOVE_EMPTY_HOSTNAME FQDN for the lease being deleted is empty: %1
+This error message is issued when a lease being deleted contains an indication
+that the DNS Update has been performed for it, but the FQDN is missing for this
+lease. This is an indication that someone may have messed up in the lease
+database.
+
+% DHCP6_DDNS_REMOVE_INVALID_HOSTNAME FQDN for the lease being deleted has invalid format: %1
+This error message is issued when a lease being deleted contains an indication
+that the DNS Update has been performed for it, but the FQDN held in the lease
+database has invalid format and can't be transformed to the canonical on-wire
+format.
+
 % DHCP6_HOOK_BUFFER_RCVD_SKIP received DHCPv6 buffer was dropped because a callout set the skip flag.
 This debug message is printed when a callout installed on buffer6_receive
 hook point set the skip flag. For this particular hook point, the

+ 432 - 14
src/bin/dhcp6/dhcp6_srv.cc

@@ -15,11 +15,13 @@
 #include <config.h>
 
 #include <asiolink/io_address.h>
+#include <dhcp_ddns/ncr_msg.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/duid.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_client_fqdn.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_iaaddr.h>
@@ -51,6 +53,7 @@
 
 using namespace isc;
 using namespace isc::asiolink;
+using namespace isc::dhcp_ddns;
 using namespace isc::dhcp;
 using namespace isc::hooks;
 using namespace isc::util;
@@ -91,6 +94,34 @@ Dhcp6Hooks Hooks;
 namespace isc {
 namespace dhcp {
 
+namespace {
+
+// The following constants describe server's behavior with respect to the
+// DHCPv6 Client FQDN Option sent by a client. They will be removed
+// when DDNS parameters for DHCPv6 are implemented with the ticket #3034.
+
+// Should server always include the FQDN option in its response, regardless
+// if it has been requested in ORO (Disabled).
+const bool FQDN_ALWAYS_INCLUDE = false;
+// Enable AAAA RR update delegation to the client (Disabled).
+const bool FQDN_ALLOW_CLIENT_UPDATE = false;
+// Globally enable updates (Enabled).
+const bool FQDN_ENABLE_UPDATE = true;
+// The partial name generated for the client if empty name has been
+// supplied.
+const char* FQDN_GENERATED_PARTIAL_NAME = "myhost";
+// Do update, even if client requested no updates with N flag (Disabled).
+const bool FQDN_OVERRIDE_NO_UPDATE = false;
+// Server performs an update when client requested delegation (Enabled).
+const bool FQDN_OVERRIDE_CLIENT_UPDATE = true;
+// The fully qualified domain-name suffix if partial name provided by
+// a client.
+const char* FQDN_PARTIAL_SUFFIX = "example.com";
+// Should server replace the domain-name supplied by the client (Disabled).
+const bool FQDN_REPLACE_CLIENT_NAME = false;
+
+}
+
 /// @brief file name of a server-id file
 ///
 /// Server must store its duid in persistent storage that must not change
@@ -268,6 +299,7 @@ bool Dhcpv6Srv::run() {
         }
 
         try {
+                NameChangeRequestPtr ncr;
             switch (query->getType()) {
             case DHCPV6_SOLICIT:
                 rsp = processSolicit(query);
@@ -383,6 +415,7 @@ bool Dhcpv6Srv::run() {
                         .arg(e.what());
                     continue;
                 }
+
             }
 
             try {
@@ -423,6 +456,9 @@ bool Dhcpv6Srv::run() {
                 LOG_ERROR(dhcp6_logger, DHCP6_PACKET_SEND_FAIL)
                     .arg(e.what());
             }
+
+            // Send NameChangeRequests to the b10-dhcp_ddns module.
+            sendNameChangeRequests();
         }
     }
 
@@ -774,7 +810,8 @@ Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
 }
 
 void
-Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
+Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
+                        const Option6ClientFqdnPtr& fqdn) {
 
     // We need to allocate addresses for all IA_NA options in the client's
     // question (i.e. SOLICIT or REQUEST) message.
@@ -829,8 +866,9 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
         switch (opt->second->getType()) {
         case D6O_IA_NA: {
             OptionPtr answer_opt = assignIA_NA(subnet, duid, question,
-                                               boost::dynamic_pointer_cast<Option6IA>(opt->second),
-                                               question);
+                                               boost::dynamic_pointer_cast<
+                                               Option6IA>(opt->second),
+                                               fqdn);
             if (answer_opt) {
                 answer->addOption(answer_opt);
             }
@@ -842,9 +880,274 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
     }
 }
 
+Option6ClientFqdnPtr
+Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question) {
+    // Get Client FQDN Option from the client's message. If this option hasn't
+    // been included, do nothing.
+    Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
+        Option6ClientFqdn>(question->getOption(D6O_CLIENT_FQDN));
+    if (!fqdn) {
+        return (fqdn);
+    }
+
+    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+              DHCP6_DDNS_RECEIVE_FQDN).arg(fqdn->toText());
+
+
+    // Prepare the FQDN option which will be included in the response to
+    // the client.
+    Option6ClientFqdnPtr fqdn_resp(new Option6ClientFqdn(*fqdn));
+    // RFC 4704, section 6. - all flags set to 0.
+    fqdn_resp->resetFlags();
+
+    // Conditions when N flag has to be set to indicate that server will not
+    // perform DNS updates:
+    // 1. Updates are globally disabled,
+    // 2. Client requested no update and server respects it,
+    // 3. Client requested that the AAAA update is delegated to the client but
+    //    server neither respects delegation of updates nor it is configured
+    //    to send update on its own when client requested delegation.
+    if (!FQDN_ENABLE_UPDATE ||
+        (fqdn->getFlag(Option6ClientFqdn::FLAG_N) &&
+         !FQDN_OVERRIDE_NO_UPDATE) ||
+        (!fqdn->getFlag(Option6ClientFqdn::FLAG_S) &&
+         !FQDN_ALLOW_CLIENT_UPDATE && !FQDN_OVERRIDE_CLIENT_UPDATE)) {
+        fqdn_resp->setFlag(Option6ClientFqdn::FLAG_N, true);
+
+    // Conditions when S flag is set to indicate that server will perform
+    // DNS update on its own:
+    // 1. Client requested that server performs DNS update and DNS updates are
+    //    globally enabled
+    // 2. Client requested that server delegates AAAA update to the client but
+    //    server doesn't respect delegation and it is configured to perform
+    //    an update on its own when client requested delegation.
+    } else if (fqdn->getFlag(Option6ClientFqdn::FLAG_S) ||
+               (!fqdn->getFlag(Option6ClientFqdn::FLAG_S) &&
+                !FQDN_ALLOW_CLIENT_UPDATE && FQDN_OVERRIDE_CLIENT_UPDATE)) {
+        fqdn_resp->setFlag(Option6ClientFqdn::FLAG_S, true);
+    }
+
+    // Server MUST set the O flag if it has overridden the client's setting
+    // of S flag.
+    if (fqdn->getFlag(Option6ClientFqdn::FLAG_S) !=
+        fqdn_resp->getFlag(Option6ClientFqdn::FLAG_S)) {
+        fqdn_resp->setFlag(Option6ClientFqdn::FLAG_O, true);
+    }
+
+    // If client supplied partial or empty domain-name, server should
+    // generate one.
+    if (fqdn->getDomainNameType() == Option6ClientFqdn::PARTIAL) {
+        std::ostringstream name;
+        if (fqdn->getDomainName().empty()) {
+            name << FQDN_GENERATED_PARTIAL_NAME;
+        } else {
+            name << fqdn->getDomainName();
+        }
+        name << "." << FQDN_PARTIAL_SUFFIX;
+        fqdn_resp->setDomainName(name.str(), Option6ClientFqdn::FULL);
+
+    // Server may be configured to replace a name supplied by a client,
+    // even if client supplied fully qualified domain-name.
+    } else if (FQDN_REPLACE_CLIENT_NAME) {
+        std::ostringstream name;
+        name << FQDN_GENERATED_PARTIAL_NAME << "." << FQDN_PARTIAL_SUFFIX;
+        fqdn_resp->setDomainName(name.str(), Option6ClientFqdn::FULL);
+
+    }
+
+    // Return the FQDN option which can be included in the server's response.
+    // Note that it doesn't have to be included, if client didn't request
+    // it using ORO and server is not configured to always include it.
+    return (fqdn_resp);
+}
+
+
+void
+Dhcpv6Srv::appendClientFqdn(const Pkt6Ptr& question,
+                            Pkt6Ptr& answer,
+                            const Option6ClientFqdnPtr& fqdn) {
+
+    // If FQDN is NULL, it means that client did not request DNS Update, plus
+    // server doesn't force updates.
+    if (fqdn) {
+        return;
+    }
+
+    // Server sends back the FQDN option to the client if client has requested
+    // it using Option Request Option. However, server may be configured to
+    // send the FQDN option in its response, regardless whether client requested
+    // it or not.
+    bool include_fqdn = FQDN_ALWAYS_INCLUDE;
+    if (!include_fqdn) {
+        OptionUint16ArrayPtr oro = boost::dynamic_pointer_cast<
+            OptionUint16Array>(question->getOption(D6O_ORO));
+        if (oro) {
+            const std::vector<uint16_t>& values = oro->getValues();
+            for (int i = 0; i < values.size(); ++i) {
+                if (values[i] == D6O_CLIENT_FQDN) {
+                    include_fqdn = true;
+                }
+            }
+        }
+    }
+
+    if (include_fqdn) {
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+                  DHCP6_DDNS_SEND_FQDN).arg(fqdn->toText());
+        answer->addOption(fqdn);
+    }
+
+}
+
+void
+Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
+                                    const Option6ClientFqdnPtr& opt_fqdn) {
+
+    // It is likely that client haven't included the FQDN option in the message
+    // and server is not configured to always update DNS. In such cases,
+    // FQDN option will be NULL. This is valid state, so we simply return.
+    if (!opt_fqdn) {
+        return;
+    }
+
+    // The response message instance is always required. For instance it
+    // holds the Client Identifier. It is a programming error if supplied
+    // message is NULL.
+    if (!answer) {
+        isc_throw(isc::Unexpected, "an instance of the object"
+                  << " encapsulating server's message must not be"
+                  << " NULL when creating DNS NameChangeRequest");
+    }
+
+    // Get the Client Id. It is mandatory and a function creating a response
+    // would have thrown an exception if it was missing. Thus throwning
+    // Unexpected if it is missing as it is a programming error.
+    OptionPtr opt_duid = answer->getOption(D6O_CLIENTID);
+    if (!opt_duid) {
+        isc_throw(isc::Unexpected,
+                  "client identifier is required when creating a new"
+                  " DNS NameChangeRequest");
+    }
+    DuidPtr duid = DuidPtr(new DUID(opt_duid->getData()));
+
+    // Get the FQDN in the on-wire format. It will be needed to compute
+    // DHCID.
+    OutputBuffer name_buf(1);
+    opt_fqdn->packDomainName(name_buf);
+    const uint8_t* name_data = static_cast<const uint8_t*>(name_buf.getData());
+    // @todo currently D2Dhcid accepts a vector holding FQDN.
+    // However, it will be faster if we used a pointer name_data.
+    std::vector<uint8_t> buf_vec(name_data, name_data + name_buf.getLength());
+    // Compute DHCID from Client Identifier and FQDN.
+    isc::dhcp_ddns::D2Dhcid dhcid(*duid, buf_vec);
+
+    // Get all IAs from the answer. For each IA, holding an address we will
+    // create a corresponding NameChangeRequest.
+    Option::OptionCollection answer_ias = answer->getOptions(D6O_IA_NA);
+    for (Option::OptionCollection::const_iterator answer_ia =
+             answer_ias.begin(); answer_ia != answer_ias.end(); ++answer_ia) {
+        // @todo IA_NA may contain multiple addresses. We should process
+        // each address individually. Currently we get only one.
+        Option6IAAddrPtr iaaddr = boost::static_pointer_cast<
+            Option6IAAddr>(answer_ia->second->getOption(D6O_IAADDR));
+        // We need an address to create a name-to-address mapping.
+        // If address is missing for any reason, go to the next IA.
+        if (!iaaddr) {
+            continue;
+        }
+        // Create new NameChangeRequest. Use the domain name from the FQDN.
+        // This is an FQDN included in the response to the client, so it
+        // holds a fully qualified domain-name already (not partial).
+        // Get the IP address from the lease. Also, use the S flag to determine
+        // if forward change should be performed. This flag will always be
+        // set if server has taken responsibility for the forward update.
+        NameChangeRequest ncr(isc::dhcp_ddns::CHG_ADD,
+                              opt_fqdn->getFlag(Option6ClientFqdn::FLAG_S),
+                              true, opt_fqdn->getDomainName(),
+                              iaaddr->getAddress().toText(),
+                              dhcid, 0, iaaddr->getValid());
+        // Add the request to the queue. This queue will be read elsewhere in
+        // the code and all requests from this queue will be sent to the
+        // D2 module.
+        name_change_reqs_.push(ncr);
+
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+                  DHCP6_DDNS_CREATE_ADD_NAME_CHANGE_REQUEST).arg(ncr.toText());
+    }
+}
+
+void
+Dhcpv6Srv::createRemovalNameChangeRequest(const Lease6Ptr& lease) {
+    // If we haven't performed a DNS Update when lease was acquired,
+    // there is nothing to do here.
+    if (!lease->fqdn_fwd_ && !lease->fqdn_rev_) {
+        return;
+    }
+
+    // When lease was added into a database the host name should have
+    // been added. The hostname can be empty if someone messed up in the
+    // lease data base and removed the hostname.
+    if (lease->hostname_.empty()) {
+        LOG_ERROR(dhcp6_logger, DHCP6_DDNS_REMOVE_EMPTY_HOSTNAME)
+            .arg(lease->addr_.toText());
+        return;
+    }
+
+    // If hostname is non-empty, try to convert it to wire format so as
+    // DHCID can be computed from it. This may throw an exception if hostname
+    // has invalid format. Again, this should be only possible in case of
+    // manual intervention in the database. Note that the last parameter
+    // passed to the writeFqdn function forces conversion of the FQDN
+    // to lower case. This is required by the RFC4701, section 3.5.
+    // The DHCID computation is further in this function.
+    std::vector<uint8_t> hostname_wire;
+    try {
+        OptionDataTypeUtil::writeFqdn(lease->hostname_, hostname_wire, true);
+    } catch (const Exception& ex) {
+        LOG_ERROR(dhcp6_logger, DHCP6_DDNS_REMOVE_INVALID_HOSTNAME)
+            .arg(lease->hostname_);
+        return;
+    }
+
+    // DUID must have been checked already  by the caller of this function.
+    // Let's be on the safe side and make sure it is non-NULL and throw
+    // an exception if it is NULL.
+    if (!lease->duid_) {
+        isc_throw(isc::Unexpected, "DUID must be set when creating"
+                  << " NameChangeRequest for DNS records removal for "
+                  << lease->addr_.toText());
+
+    }
+    isc::dhcp_ddns::D2Dhcid dhcid(*lease->duid_, hostname_wire);
+
+    // Create a NameChangeRequest to remove the entry.
+    NameChangeRequest ncr(isc::dhcp_ddns::CHG_REMOVE,
+                          lease->fqdn_fwd_, lease->fqdn_rev_,
+                          lease->hostname_,
+                          lease->addr_.toText(),
+                          dhcid, 0, lease->valid_lft_);
+    name_change_reqs_.push(ncr);
+
+    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+              DHCP6_DDNS_CREATE_REMOVE_NAME_CHANGE_REQUEST).arg(ncr.toText());
+
+}
+
+void
+Dhcpv6Srv::sendNameChangeRequests() {
+    while (!name_change_reqs_.empty()) {
+        // @todo Once next NameChangeRequest is picked from the queue
+        // we should send it to the bind10-dhcp_ddns module. Currently we
+        // just drop it.
+        name_change_reqs_.pop();
+    }
+}
+
+
 OptionPtr
 Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                       Pkt6Ptr question, boost::shared_ptr<Option6IA> ia, const Pkt6Ptr& query) {
+                       const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia,
+                       const Option6ClientFqdnPtr& fqdn) {
     // If there is no subnet selected for handling this IA_NA, the only thing to do left is
     // to say that we are sorry, but the user won't get an address. As a convenience, we
     // use a different status text to indicate that (compare to the same status code,
@@ -883,19 +1186,48 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
     // it should include this hint. That will help us during the actual lease
     // allocation.
     bool fake_allocation = false;
-    if (question->getType() == DHCPV6_SOLICIT) {
+    if (query->getType() == DHCPV6_SOLICIT) {
         /// @todo: Check if we support rapid commit
         fake_allocation = true;
     }
 
     CalloutHandlePtr callout_handle = getCalloutHandle(query);
 
+    // At this point, we have to make make some decisions with respect to the
+    // FQDN option that we have generated as a result of receiving client's
+    // FQDN. In particular, we have to get to know if the DNS update will be
+    // performed or not. It is possible that option is NULL, which is valid
+    // condition if client didn't request DNS updates and server didn't force
+    // the update.
+    bool do_fwd = false;
+    bool do_rev = false;
+    if (fqdn) {
+        // Flag S must not coexist with flag N being set to 1, so if S=1
+        // server takes responsibility for both reverse and forward updates.
+        // Otherwise, we have to check N.
+        if (fqdn->getFlag(Option6ClientFqdn::FLAG_S)) {
+            do_fwd = true;
+            do_rev = true;
+        } else if (!fqdn->getFlag(Option6ClientFqdn::FLAG_N)) {
+            do_rev = true;
+        }
+    }
+    // Set hostname only in case any of the updates is being performed.
+    std::string hostname;
+    if (do_fwd || do_rev) {
+        hostname = fqdn->getDomainName();
+    }
+
     // Use allocation engine to pick a lease for this client. Allocation engine
     // will try to honour the hint, but it is just a hint - some other address
     // may be used instead. If fake_allocation is set to false, the lease will
     // be inserted into the LeaseMgr as well.
-    Lease6Ptr lease = alloc_engine_->allocateAddress6(subnet, duid, ia->getIAID(),
-                                                      hint, fake_allocation,
+    Lease6Ptr lease = alloc_engine_->allocateAddress6(subnet, duid,
+                                                      ia->getIAID(),
+                                                      hint,
+                                                      do_fwd, do_rev,
+                                                      hostname,
+                                                      fake_allocation,
                                                       callout_handle);
 
     // Create IA_NA that we will put in the response.
@@ -925,6 +1257,29 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
         // It would be possible to insert status code=0(success) as well,
         // but this is considered waste of bandwidth as absence of status
         // code is considered a success.
+
+        // Allocation engine may have returned an existing lease. If so, we
+        // have to check that the FQDN settings we provided are the same
+        // that were set. If they aren't, we will have to remove existing
+        // DNS records and update the lease with the new settings.
+        if ((lease->hostname_ != hostname) || (lease->fqdn_fwd_ != do_fwd) ||
+            (lease->fqdn_rev_ != do_rev)) {
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+                      DHCP6_DDNS_LEASE_ASSIGN_FQDN_CHANGE)
+                .arg(lease->toText())
+                .arg(hostname)
+                .arg(do_rev ? "true" : "false")
+                .arg(do_fwd ? "true" : "false");
+
+            // Schedule removal of the existing lease.
+            createRemovalNameChangeRequest(lease);
+            // Set the new lease properties and update.
+            lease->hostname_ = hostname;
+            lease->fqdn_fwd_ = do_fwd;
+            lease->fqdn_rev_ = do_rev;
+            LeaseMgrFactory::instance().updateLease6(lease);
+        }
+
     } else {
         // Allocation engine did not allocate a lease. The engine logged
         // cause of that failure. The only thing left is to insert
@@ -943,7 +1298,8 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
 
 OptionPtr
 Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                      const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia) {
+                      const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia,
+                      const Option6ClientFqdnPtr& fqdn) {
     if (!subnet) {
         // There's no subnet select for this client. There's nothing to renew.
         boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
@@ -983,11 +1339,50 @@ Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
     // Keep the old data in case the callout tells us to skip update
     Lease6 old_data = *lease;
 
+    // At this point, we have to make make some decisions with respect to the
+    // FQDN option that we have generated as a result of receiving client's
+    // FQDN. In particular, we have to get to know if the DNS update will be
+    // performed or not. It is possible that option is NULL, which is valid
+    // condition if client didn't request DNS updates and server didn't force
+    // the update.
+    bool do_fwd = false;
+    bool do_rev = false;
+    if (fqdn) {
+        if (fqdn->getFlag(Option6ClientFqdn::FLAG_S)) {
+            do_fwd = true;
+            do_rev = true;
+        } else if (!fqdn->getFlag(Option6ClientFqdn::FLAG_N)) {
+            do_rev = true;
+        }
+    }
+
+    std::string hostname;
+    if (do_fwd || do_rev) {
+        hostname = fqdn->getDomainName();
+    }
+
+    // If the new FQDN settings have changed for the lease, we need to
+    // delete any existing FQDN records for this lease.
+    if ((lease->hostname_ != hostname) || (lease->fqdn_fwd_ != do_fwd) ||
+        (lease->fqdn_rev_ != do_rev)) {
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+                  DHCP6_DDNS_LEASE_RENEW_FQDN_CHANGE)
+            .arg(lease->toText())
+            .arg(hostname)
+            .arg(do_rev ? "true" : "false")
+            .arg(do_fwd ? "true" : "false");
+
+        createRemovalNameChangeRequest(lease);
+    }
+
     lease->preferred_lft_ = subnet->getPreferred();
     lease->valid_lft_ = subnet->getValid();
     lease->t1_ = subnet->getT1();
     lease->t2_ = subnet->getT2();
     lease->cltt_ = time(NULL);
+    lease->hostname_ = hostname;
+    lease->fqdn_fwd_ = do_fwd;
+    lease->fqdn_rev_ = do_rev;
 
     // Create empty IA_NA option with IAID matching the request.
     boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
@@ -1043,7 +1438,8 @@ Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
 }
 
 void
-Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
+Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply,
+                       const Option6ClientFqdnPtr& fqdn) {
 
     // We need to renew addresses for all IA_NA options in the client's
     // RENEW message.
@@ -1087,7 +1483,9 @@ Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
         switch (opt->second->getType()) {
         case D6O_IA_NA: {
             OptionPtr answer_opt = renewIA_NA(subnet, duid, renew,
-                                   boost::dynamic_pointer_cast<Option6IA>(opt->second));
+                                              boost::dynamic_pointer_cast<
+                                              Option6IA>(opt->second),
+                                              fqdn);
             if (answer_opt) {
                 reply->addOption(answer_opt);
             }
@@ -1297,6 +1695,11 @@ Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, const Pkt6Ptr& query,
         ia_rsp->addOption(createStatusCode(STATUS_Success,
                           "Lease released. Thank you, please come again."));
 
+        // Check if a lease has flags indicating that the FQDN update has
+        // been performed. If so, create NameChangeRequest which removes
+        // the entries.
+        createRemovalNameChangeRequest(lease);
+
         return (ia_rsp);
     }
 }
@@ -1312,7 +1715,12 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
     appendDefaultOptions(solicit, advertise);
     appendRequestedOptions(solicit, advertise);
 
-    assignLeases(solicit, advertise);
+    Option6ClientFqdnPtr fqdn = processClientFqdn(solicit);
+    assignLeases(solicit, advertise, fqdn);
+    appendClientFqdn(solicit, advertise, fqdn);
+    // Note, that we don't create NameChangeRequests here because we don't
+    // perform DNS Updates for Solicit. Client must send Request to update
+    // DNS.
 
     return (advertise);
 }
@@ -1328,7 +1736,10 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
     appendDefaultOptions(request, reply);
     appendRequestedOptions(request, reply);
 
-    assignLeases(request, reply);
+    Option6ClientFqdnPtr fqdn = processClientFqdn(request);
+    assignLeases(request, reply, fqdn);
+    appendClientFqdn(request, reply, fqdn);
+    createNameChangeRequests(reply, fqdn);
 
     return (reply);
 }
@@ -1344,13 +1755,17 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
     appendDefaultOptions(renew, reply);
     appendRequestedOptions(renew, reply);
 
-    renewLeases(renew, reply);
+    Option6ClientFqdnPtr fqdn = processClientFqdn(renew);
+    renewLeases(renew, reply, fqdn);
+    appendClientFqdn(renew, reply, fqdn);
+    createNameChangeRequests(reply, fqdn);
 
     return reply;
 }
 
 Pkt6Ptr
 Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
+
     /// @todo: Implement this
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid()));
     return reply;
@@ -1375,7 +1790,10 @@ Dhcpv6Srv::processRelease(const Pkt6Ptr& release) {
 
     releaseLeases(release, reply);
 
-    return reply;
+    // @todo If client sent a release and we should remove outstanding
+    // DNS records.
+
+    return (reply);
 }
 
 Pkt6Ptr

+ 113 - 10
src/bin/dhcp6/dhcp6_srv.h

@@ -15,9 +15,11 @@
 #ifndef DHCPV6_SRV_H
 #define DHCPV6_SRV_H
 
+#include <dhcp_ddns/ncr_msg.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/duid.h>
 #include <dhcp/option.h>
+#include <dhcp/option6_client_fqdn.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option_definition.h>
 #include <dhcp/pkt6.h>
@@ -28,6 +30,7 @@
 #include <boost/noncopyable.hpp>
 
 #include <iostream>
+#include <queue>
 
 namespace isc {
 namespace dhcp {
@@ -79,7 +82,7 @@ public:
     ///
     /// Main server processing loop. Receives incoming packets, verifies
     /// their correctness, generates appropriate answer (if needed) and
-    /// transmits respones.
+    /// transmits responses.
     ///
     /// @return true, if being shut down gracefully, fail if experienced
     ///         critical error.
@@ -132,7 +135,7 @@ protected:
     ///
     /// Processes received SOLICIT message and verifies that its sender
     /// should be served. In particular IA, TA and PD options are populated
-    /// with to-be assinged addresses, temporary addresses and delegated
+    /// with to-be assigned addresses, temporary addresses and delegated
     /// prefixes, respectively. In the usual 4 message exchange, server is
     /// expected to respond with ADVERTISE message. However, if client
     /// requests rapid-commit and server supports it, REPLY will be sent
@@ -148,7 +151,7 @@ protected:
     ///
     /// Processes incoming REQUEST message and verifies that its sender
     /// should be served. In particular IA, TA and PD options are populated
-    /// with assinged addresses, temporary addresses and delegated
+    /// with assigned addresses, temporary addresses and delegated
     /// prefixes, respectively. Uses LeaseMgr to allocate or update existing
     /// leases.
     ///
@@ -210,14 +213,16 @@ protected:
     ///
     /// @param subnet subnet the client is connected to
     /// @param duid client's duid
-    /// @param question client's message (typically SOLICIT or REQUEST)
+    /// @param query client's message (typically SOLICIT or REQUEST)
     /// @param ia pointer to client's IA_NA option (client's request)
+    /// @param fqdn A DHCPv6 Client FQDN %Option generated in a response to the
+    /// FQDN option sent by a client.
     /// @return IA_NA option (server's response)
     OptionPtr assignIA_NA(const isc::dhcp::Subnet6Ptr& subnet,
                           const isc::dhcp::DuidPtr& duid,
-                          isc::dhcp::Pkt6Ptr question,
-                          boost::shared_ptr<Option6IA> ia,
-                          const Pkt6Ptr& query);
+                          const isc::dhcp::Pkt6Ptr& query,
+                          Option6IAPtr ia,
+                          const Option6ClientFqdnPtr& fqdn);
 
     /// @brief Renews specific IA_NA option
     ///
@@ -229,9 +234,11 @@ protected:
     /// @param duid client's duid
     /// @param query client's message
     /// @param ia IA_NA option that is being renewed
+    /// @param fqdn DHCPv6 Client FQDN Option included in the server's response
     /// @return IA_NA option (server's response)
     OptionPtr renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                         const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia);
+                         const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia,
+                         const Option6ClientFqdnPtr& fqdn);
 
     /// @brief Releases specific IA_NA option
     ///
@@ -290,7 +297,94 @@ protected:
     ///
     /// @param question client's message (with requested IA_NA)
     /// @param answer server's message (IA_NA options will be added here)
-    void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer);
+    /// @param fqdn an FQDN option generated in a response to the client's
+    /// FQDN option.
+    void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
+                      const Option6ClientFqdnPtr& fqdn);
+
+    /// @brief Processes Client FQDN Option.
+    ///
+    /// This function retrieves DHCPv6 Client FQDN %Option (if any) from the
+    /// packet sent by a client and takes necessary actions upon this option.
+    /// Received option comprises flags field which controls what DNS updates
+    /// server should do. Server may override client's preference based on
+    /// the current configuration. Server indicates that it has overridden
+    /// the preference by storing DHCPv6 Client Fqdn %Option with the
+    /// appropriate flags in the response to a client. This option is also
+    /// used to communicate the client's domain-name which should be sent
+    /// to the DNS in the update. Again, server may act upon the received
+    /// domain-name, i.e. if the provided domain-name is partial it should
+    /// generate the fully qualified domain-name.
+    ///
+    /// All the logic required to form appropriate answer to the client is
+    /// held in this function.
+    ///
+    /// @param question Client's message.
+    ///
+    /// @return FQDN option produced in the response to the client's message.
+    Option6ClientFqdnPtr processClientFqdn(const Pkt6Ptr& question);
+
+    /// @brief Adds DHCPv6 Client FQDN %Option to the server response.
+    ///
+    /// This function will add the specified FQDN option into the server's
+    /// response when FQDN is not NULL and server is either configured to
+    /// always include the FQDN in the response or client requested it using
+    /// %Option Request %Option.
+    /// This function is exception safe.
+    ///
+    /// @param question A message received from the client.
+    /// @param [out] answer A server's response where FQDN option will be added.
+    /// @param fqdn A DHCPv6 Client FQDN %Option to be added to the server's
+    /// response to a client.
+    void appendClientFqdn(const Pkt6Ptr& question,
+                          Pkt6Ptr& answer,
+                          const Option6ClientFqdnPtr& fqdn);
+
+    /// @brief Creates a number of @c isc::dhcp_ddns::NameChangeRequest objects
+    /// based on the DHCPv6 Client FQDN %Option.
+    ///
+    /// The @c isc::dhcp_ddns::NameChangeRequest class encapsulates the request
+    /// from the DHCPv6 server to the DHCP-DDNS module to perform DNS Update.
+    /// The FQDN option carries response to the client about DNS updates that
+    /// server intents to perform for the DNS client. Based on this, the
+    /// function will create zero or more @c isc::dhcp_ddns::NameChangeRequest
+    /// objects and store them in the internal queue. Requests created by this
+    /// function are only adding or updating DNS records. In order to generate
+    /// requests for DNS records removal, use @c createRemovalNameChangeRequest.
+    ///
+    /// @todo Add support for multiple IAADDR options in the IA_NA.
+    ///
+    /// @param answer A message beging sent to the Client.
+    /// @param fqdn_answer A DHCPv6 Client FQDN %Option which is included in the
+    /// response message sent to a client.
+    void createNameChangeRequests(const Pkt6Ptr& answer,
+                                  const Option6ClientFqdnPtr& fqdn_answer);
+
+    /// @brief Creates a @c isc::dhcp_ddns::NameChangeRequest which requests
+    /// removal of DNS entries for a particular lease.
+    ///
+    /// This function should be called upon removal of the lease from the lease
+    /// database, i.e, when client sent Release or Decline message. It will
+    /// create a single @c isc::dhcp_ddns::NameChangeRequest which removes the
+    /// existing DNS records for the lease, which server is responsible for.
+    /// Note that this function will not remove the entries which server hadn't
+    /// added. This is the case, when client performs forward DNS update on its
+    /// own.
+    ///
+    /// @param lease A lease for which the the removal of corresponding DNS
+    /// records will be performed.
+    void createRemovalNameChangeRequest(const Lease6Ptr& lease);
+
+    /// @brief Sends all outstanding NameChangeRequests to bind10-d2 module.
+    ///
+    /// The purpose of this function is to pick all outstanding
+    /// NameChangeRequests from the FIFO queue and send them to bind10-dhcp-ddns
+    /// module.
+    ///
+    /// @todo Currently this function simply removes all requests from the
+    /// queue but doesn't send them anywhere. In the future, the
+    /// NameChangeSender will be used to deliver requests to the other module.
+    void sendNameChangeRequests();
 
     /// @brief Attempts to renew received addresses
     ///
@@ -300,7 +394,10 @@ protected:
     /// as IA_NA/IAADDR to reply packet.
     /// @param renew client's message asking for renew
     /// @param reply server's response
-    void renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply);
+    /// @param fqdn A DHCPv6 Client FQDN %Option generated in the response to the
+    /// client's FQDN option.
+    void renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply,
+                     const Option6ClientFqdnPtr& fqdn);
 
     /// @brief Attempts to release received addresses
     ///
@@ -385,6 +482,12 @@ private:
 
     /// UDP port number on which server listens.
     uint16_t port_;
+
+protected:
+
+    /// Holds a list of @c isc::dhcp_ddns::NameChangeRequest objects, which
+    /// are waiting for sending to b10-dhcp-ddns module.
+    std::queue<isc::dhcp_ddns::NameChangeRequest> name_change_reqs_;
 };
 
 }; // namespace isc::dhcp

+ 1 - 0
src/bin/dhcp6/tests/Makefile.am

@@ -79,6 +79,7 @@ dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la

+ 619 - 0
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -15,11 +15,13 @@
 #include <config.h>
 
 #include <asiolink/io_address.h>
+#include <dhcp_ddns/ncr_msg.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/duid.h>
 #include <dhcp/option.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_client_fqdn.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option_int_array.h>
@@ -35,6 +37,7 @@
 #include <hooks/server_hooks.h>
 
 #include <dhcp6/tests/dhcp6_test_utils.h>
+#include <boost/pointer_cast.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 #include <unistd.h>
@@ -46,6 +49,7 @@ using namespace isc;
 using namespace isc::test;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
+using namespace isc::dhcp_ddns;
 using namespace isc::util;
 using namespace isc::hooks;
 using namespace std;
@@ -54,6 +58,249 @@ using namespace std;
 // Maybe it should be isc::test?
 namespace {
 
+// This is a test fixture class for testing the processing of the DHCPv6 Client
+// FQDN Option.
+class FqdnDhcpv6SrvTest : public Dhcpv6SrvTest {
+public:
+    // Constructor
+    FqdnDhcpv6SrvTest()
+        : Dhcpv6SrvTest() {
+        // generateClientId assigns DUID to duid_.
+        generateClientId();
+        lease_.reset(new Lease6(Lease6::LEASE_IA_NA, IOAddress("2001:db8:1::1"),
+                                duid_, 1234, 501, 502, 503,
+                                504, 1, 0));
+
+    }
+
+    // Destructor
+    virtual ~FqdnDhcpv6SrvTest() {
+    }
+
+    // Construct the DHCPv6 Client FQDN Option using flags and domain-name.
+    Option6ClientFqdnPtr
+    createClientFqdn(const uint8_t flags,
+                     const std::string& fqdn_name,
+                     const Option6ClientFqdn::DomainNameType fqdn_type) {
+        return (Option6ClientFqdnPtr(new Option6ClientFqdn(flags,
+                                                           fqdn_name,
+                                                           fqdn_type)));
+    }
+
+    // Create a message holding DHCPv6 Client FQDN Option.
+    Pkt6Ptr generatePktWithFqdn(uint8_t msg_type,
+                                const uint8_t fqdn_flags,
+                                const std::string& fqdn_domain_name,
+                                const Option6ClientFqdn::DomainNameType
+                                fqdn_type,
+                                const bool include_oro,
+                                OptionPtr srvid = OptionPtr()) {
+        Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(msg_type, 1234));
+        pkt->setRemoteAddr(IOAddress("fe80::abcd"));
+        Option6IAPtr ia = generateIA(234, 1500, 3000);
+
+        if (msg_type != DHCPV6_REPLY) {
+            IOAddress hint("2001:db8:1:1::dead:beef");
+            OptionPtr hint_opt(new Option6IAAddr(D6O_IAADDR, hint, 300, 500));
+            ia->addOption(hint_opt);
+            pkt->addOption(ia);
+        }
+
+        OptionPtr clientid = generateClientId();
+        pkt->addOption(clientid);
+        if (srvid && (msg_type != DHCPV6_SOLICIT)) {
+            pkt->addOption(srvid);
+        }
+
+        pkt->addOption(createClientFqdn(fqdn_flags, fqdn_domain_name,
+                                        fqdn_type));
+
+        if (include_oro) {
+            OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6,
+                                                           D6O_ORO));
+            oro->addValue(D6O_CLIENT_FQDN);
+            pkt->addOption(oro);
+        }
+
+        return (pkt);
+    }
+
+    // Creates instance of the DHCPv6 message with client id and server id.
+    Pkt6Ptr generateMessageWithIds(const uint8_t msg_type,
+                                   NakedDhcpv6Srv& srv) {
+        Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(msg_type, 1234));
+        // Generate client-id.
+        OptionPtr opt_clientid = generateClientId();
+        pkt->addOption(opt_clientid);
+
+        if (msg_type != DHCPV6_SOLICIT) {
+            // Generate server-id.
+            pkt->addOption(srv.getServerID());
+        }
+
+        return (pkt);
+    }
+
+    // Returns an instance of the option carrying FQDN.
+    Option6ClientFqdnPtr getClientFqdnOption(const Pkt6Ptr& pkt) {
+        return (boost::dynamic_pointer_cast<Option6ClientFqdn>
+                (pkt->getOption(D6O_CLIENT_FQDN)));
+    }
+
+    // Adds IA option to the message. Option holds an address.
+    void addIA(const uint32_t iaid, const IOAddress& addr, Pkt6Ptr& pkt) {
+        Option6IAPtr opt_ia = generateIA(iaid, 1500, 3000);
+        Option6IAAddrPtr opt_iaaddr(new Option6IAAddr(D6O_IAADDR, addr,
+                                                      300, 500));
+        opt_ia->addOption(opt_iaaddr);
+        pkt->addOption(opt_ia);
+    }
+
+    // Adds IA option to the message. Option holds status code.
+    void addIA(const uint32_t iaid, const uint16_t status_code, Pkt6Ptr& pkt) {
+        Option6IAPtr opt_ia = generateIA(iaid, 1500, 3000);
+        addStatusCode(status_code, "", opt_ia);
+        pkt->addOption(opt_ia);
+    }
+
+    // Creates status code with the specified code and message.
+    OptionCustomPtr createStatusCode(const uint16_t code,
+                                     const std::string& msg) {
+        OptionDefinition def("status-code", D6O_STATUS_CODE, "record");
+        def.addRecordField("uint16");
+        def.addRecordField("string");
+        OptionCustomPtr opt_status(new OptionCustom(def, Option::V6));
+        opt_status->writeInteger(code);
+        if (!msg.empty()) {
+            opt_status->writeString(msg, 1);
+        }
+        return (opt_status);
+    }
+
+    // Adds Status Code option to the IA.
+    void addStatusCode(const uint16_t code, const std::string& msg,
+                       Option6IAPtr& opt_ia) {
+        opt_ia->addOption(createStatusCode(code, msg));
+    }
+
+    // Test processing of the DHCPv6 Client FQDN Option.
+    void testFqdn(const uint16_t msg_type,
+                  const bool use_oro,
+                  const uint8_t in_flags,
+                  const std::string& in_domain_name,
+                  const Option6ClientFqdn::DomainNameType in_domain_type,
+                  const uint8_t exp_flags,
+                  const std::string& exp_domain_name) {
+        NakedDhcpv6Srv srv(0);
+        Pkt6Ptr question = generatePktWithFqdn(msg_type,
+                                               in_flags,
+                                               in_domain_name,
+                                               in_domain_type,
+                                               use_oro);
+        ASSERT_TRUE(getClientFqdnOption(question));
+
+        Option6ClientFqdnPtr answ_fqdn;
+        ASSERT_NO_THROW(answ_fqdn = srv.processClientFqdn(question));
+        ASSERT_TRUE(answ_fqdn);
+
+        const bool flag_n = (exp_flags & Option6ClientFqdn::FLAG_N) != 0;
+        const bool flag_s = (exp_flags & Option6ClientFqdn::FLAG_S) != 0;
+        const bool flag_o = (exp_flags & Option6ClientFqdn::FLAG_O) != 0;
+
+        EXPECT_EQ(flag_n, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_N));
+        EXPECT_EQ(flag_s, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_S));
+        EXPECT_EQ(flag_o, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_O));
+
+        EXPECT_EQ(exp_domain_name, answ_fqdn->getDomainName());
+        EXPECT_EQ(Option6ClientFqdn::FULL, answ_fqdn->getDomainNameType());
+    }
+
+    // Tests that the client message holding an FQDN is processed and the
+    // lease is acquired.
+    void testProcessMessage(const uint8_t msg_type,
+                            const std::string& hostname,
+                            NakedDhcpv6Srv& srv) {
+        // Create a message of a specified type, add server id and
+        // FQDN option.
+        OptionPtr srvid = srv.getServerID();
+        Pkt6Ptr req = generatePktWithFqdn(msg_type, Option6ClientFqdn::FLAG_S,
+                                          hostname,
+                                          Option6ClientFqdn::FULL,
+                                          true, srvid);
+
+        // For different client's message types we have to invoke different
+        // functions to generate response.
+        Pkt6Ptr reply;
+        if (msg_type == DHCPV6_SOLICIT) {
+            ASSERT_NO_THROW(reply = srv.processSolicit(req));
+
+        } else if (msg_type == DHCPV6_REQUEST) {
+            ASSERT_NO_THROW(reply = srv.processRequest(req));
+
+        } else if (msg_type == DHCPV6_RENEW) {
+            ASSERT_NO_THROW(reply = srv.processRequest(req));
+
+        } else if (msg_type == DHCPV6_RELEASE) {
+            // For Release no lease will be acquired so we have to leave
+            // function here.
+            ASSERT_NO_THROW(reply = srv.processRelease(req));
+            return;
+        } else {
+            // We are not interested in testing other message types.
+            return;
+        }
+
+        // For Solicit, we will get different message type obviously.
+        if (msg_type == DHCPV6_SOLICIT) {
+            checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+        } else {
+            checkResponse(reply, DHCPV6_REPLY, 1234);
+        }
+
+        // Check verify that IA_NA is correct.
+        Option6IAAddrPtr addr =
+            checkIA_NA(reply, 234, subnet_->getT1(), subnet_->getT2());
+        ASSERT_TRUE(addr);
+
+        // Check that we have got the address we requested.
+        checkIAAddr(addr, IOAddress("2001:db8:1:1::dead:beef"),
+                    subnet_->getPreferred(),
+                    subnet_->getValid());
+
+        if (msg_type != DHCPV6_SOLICIT) {
+            // Check that the lease exists.
+            Lease6Ptr lease =
+                checkLease(duid_, reply->getOption(D6O_IA_NA), addr);
+            ASSERT_TRUE(lease);
+        }
+    }
+
+    // Verify that NameChangeRequest holds valid values.
+    void verifyNameChangeRequest(NakedDhcpv6Srv& srv,
+                                 const isc::dhcp_ddns::NameChangeType type,
+                                 const bool reverse, const bool forward,
+                                 const std::string& addr,
+                                 const std::string& dhcid,
+                                 const uint16_t expires,
+                                 const uint16_t len) {
+        NameChangeRequest ncr = srv.name_change_reqs_.front();
+        EXPECT_EQ(type, ncr.getChangeType());
+        EXPECT_EQ(forward, ncr.isForwardChange());
+        EXPECT_EQ(reverse, ncr.isReverseChange());
+        EXPECT_EQ(addr, ncr.getIpAddress());
+        EXPECT_EQ(dhcid, ncr.getDhcid().toStr());
+        EXPECT_EQ(expires, ncr.getLeaseExpiresOn());
+        EXPECT_EQ(len, ncr.getLeaseLength());
+        EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr.getStatus());
+        srv.name_change_reqs_.pop();
+    }
+
+    // Holds a lease used by a test.
+    Lease6Ptr lease_;
+
+};
+
 // This test verifies that incoming SOLICIT can be handled properly when
 // there are no subnets configured.
 //
@@ -1481,6 +1728,378 @@ TEST_F(Dhcpv6SrvTest, ServerID) {
     EXPECT_EQ(duid1_text, text);
 }
 
+// A set of tests verifying server's behaviour when it receives the DHCPv6
+// Client Fqdn Option.
+// @todo: Extend these tests once appropriate configuration parameters are
+// implemented (ticket #3034).
+
+// Test server's response when client requests that server performs AAAA update.
+TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdate) {
+    testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_S,
+             "myhost.example.com",
+             Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_S,
+             "myhost.example.com.");
+}
+
+// Test server's response when client provides partial domain-name and requests
+// that server performs AAAA update.
+TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdatePartialName) {
+    testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_S, "myhost",
+             Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S,
+             "myhost.example.com.");
+}
+
+// Test server's response when client provides empty domain-name and requests
+// that server performs AAAA update.
+TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdateNoName) {
+    testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_S, "",
+             Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S,
+             "myhost.example.com.");
+}
+
+// Test server's response when client requests no DNS update.
+TEST_F(FqdnDhcpv6SrvTest, noUpdate) {
+    testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_N,
+             "myhost.example.com",
+             Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_N,
+             "myhost.example.com.");
+}
+
+// Test server's response when client requests that server delegates the AAAA
+// update to the client and this delegation is not allowed.
+TEST_F(FqdnDhcpv6SrvTest, clientAAAAUpdateNotAllowed) {
+    testFqdn(DHCPV6_SOLICIT, true, 0, "myhost.example.com.",
+             Option6ClientFqdn::FULL,
+             Option6ClientFqdn::FLAG_S | Option6ClientFqdn::FLAG_O,
+             "myhost.example.com.");
+}
+
+// Test that exception is thrown if supplied NULL answer packet when
+// creating NameChangeRequests.
+TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAnswer) {
+    NakedDhcpv6Srv srv(0);
+
+    Pkt6Ptr answer;
+    Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
+                                                 "myhost.example.com",
+                                                 Option6ClientFqdn::FULL);
+    EXPECT_THROW(srv.createNameChangeRequests(answer, fqdn),
+                 isc::Unexpected);
+
+}
+
+// Test that exception is thrown if supplied answer from the server
+// contains no DUID when creating NameChangeRequests.
+TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoDUID) {
+    NakedDhcpv6Srv srv(0);
+
+    Pkt6Ptr answer = Pkt6Ptr(new Pkt6(DHCPV6_REPLY, 1234));
+    Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
+                                                 "myhost.example.com",
+                                                 Option6ClientFqdn::FULL);
+
+    EXPECT_THROW(srv.createNameChangeRequests(answer, fqdn),
+                 isc::Unexpected);
+
+}
+
+// Test no NameChangeRequests are added if FQDN option is NULL.
+TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoFQDN) {
+    NakedDhcpv6Srv srv(0);
+
+    // Create Reply message with Client Id and Server id.
+    Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
+
+    // Pass NULL FQDN option. No NameChangeRequests should be created.
+    Option6ClientFqdnPtr fqdn;
+    ASSERT_NO_THROW(srv.createNameChangeRequests(answer, fqdn));
+
+    // There should be no new NameChangeRequests.
+    EXPECT_TRUE(srv.name_change_reqs_.empty());
+}
+
+// Test that NameChangeRequests are not generated if an answer message
+// contains no addresses.
+TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAddr) {
+    NakedDhcpv6Srv srv(0);
+
+    // Create Reply message with Client Id and Server id.
+    Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
+
+    Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
+                                                 "myhost.example.com",
+                                                 Option6ClientFqdn::FULL);
+
+    ASSERT_NO_THROW(srv.createNameChangeRequests(answer, fqdn));
+
+    // We didn't add any IAs, so there should be no NameChangeRequests in th
+    // queue.
+    ASSERT_TRUE(srv.name_change_reqs_.empty());
+}
+
+// Test that a number of NameChangeRequests is created as a result of
+// processing the answer message which holds 3 IAs and when FQDN is
+// specified.
+TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequests) {
+    NakedDhcpv6Srv srv(0);
+
+    // Create Reply message with Client Id and Server id.
+    Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
+
+    // Create three IAs, each having different address.
+    addIA(1234, IOAddress("2001:db8:1::1"), answer);
+    addIA(2345, IOAddress("2001:db8:1::2"), answer);
+    addIA(3456, IOAddress("2001:db8:1::3"), answer);
+
+    // Use domain name in upper case. It should be converted to lower-case
+    // before DHCID is calculated. So, we should get the same result as if
+    // we typed domain name in lower-case.
+    Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
+                                                 "MYHOST.EXAMPLE.COM",
+                                                 Option6ClientFqdn::FULL);
+
+    // Create NameChangeRequests. Since we have added 3 IAs, it should
+    // result in generation of 3 distinct NameChangeRequests.
+    ASSERT_NO_THROW(srv.createNameChangeRequests(answer, fqdn));
+    ASSERT_EQ(3, srv.name_change_reqs_.size());
+
+    // Verify that NameChangeRequests are correct. Each call to the
+    // verifyNameChangeRequest will pop verified request from the queue.
+
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                            "2001:db8:1::1",
+                            "000201415AA33D1187D148275136FA30300478"
+                            "FAAAA3EBD29826B5C907B2C9268A6F52",
+                            0, 500);
+
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                            "2001:db8:1::2",
+                            "000201415AA33D1187D148275136FA30300478"
+                            "FAAAA3EBD29826B5C907B2C9268A6F52",
+                            0, 500);
+
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                            "2001:db8:1::3",
+                            "000201415AA33D1187D148275136FA30300478"
+                            "FAAAA3EBD29826B5C907B2C9268A6F52",
+                            0, 500);
+
+}
+
+// Test creation of the NameChangeRequest to remove both forward and reverse
+// mapping for the given lease.
+TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestFwdRev) {
+    NakedDhcpv6Srv srv(0);
+
+    lease_->fqdn_fwd_ = true;
+    lease_->fqdn_rev_ = true;
+    // Part of the domain name is in upper case, to test that it gets converted
+    // to lower case before DHCID is computed. So, we should get the same DHCID
+    // as if we typed domain-name in lower case.
+    lease_->hostname_ = "MYHOST.example.com.";
+
+    ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
+
+    ASSERT_EQ(1, srv.name_change_reqs_.size());
+
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+                            "2001:db8:1::1",
+                            "000201415AA33D1187D148275136FA30300478"
+                            "FAAAA3EBD29826B5C907B2C9268A6F52",
+                            0, 502);
+
+}
+
+// Test creation of the NameChangeRequest to remove reverse mapping for the
+// given lease.
+TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestRev) {
+    NakedDhcpv6Srv srv(0);
+
+    lease_->fqdn_fwd_ = false;
+    lease_->fqdn_rev_ = true;
+    lease_->hostname_ = "myhost.example.com.";
+
+    ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
+
+    ASSERT_EQ(1, srv.name_change_reqs_.size());
+
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, false,
+                            "2001:db8:1::1",
+                            "000201415AA33D1187D148275136FA30300478"
+                            "FAAAA3EBD29826B5C907B2C9268A6F52",
+                            0, 502);
+
+}
+
+// Test that NameChangeRequest to remove DNS records is not generated when
+// neither forward nor reverse DNS update has been performed for a lease.
+TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestNoUpdate) {
+    NakedDhcpv6Srv srv(0);
+
+    lease_->fqdn_fwd_ = false;
+    lease_->fqdn_rev_ = false;
+
+    ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
+
+    EXPECT_TRUE(srv.name_change_reqs_.empty());
+
+}
+
+// Test that NameChangeRequest is not generated if the hostname hasn't been
+// specified for a lease for which forward and reverse mapping has been set.
+TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestNoHostname) {
+    NakedDhcpv6Srv srv(0);
+
+    lease_->fqdn_fwd_ = true;
+    lease_->fqdn_rev_ = true;
+    lease_->hostname_ = "";
+
+    ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
+
+    EXPECT_TRUE(srv.name_change_reqs_.empty());
+
+}
+
+// Test that NameChangeRequest is not generated if the invalid hostname has
+// been specified for a lease for which forward and reverse mapping has been
+// set.
+TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestWrongHostname) {
+    NakedDhcpv6Srv srv(0);
+
+    lease_->fqdn_fwd_ = true;
+    lease_->fqdn_rev_ = true;
+    lease_->hostname_ = "myhost..example.com.";
+
+    ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
+
+    EXPECT_TRUE(srv.name_change_reqs_.empty());
+
+}
+
+// Test that Advertise message generated in a response to the Solicit will
+// not result in generation if the NameChangeRequests.
+TEST_F(FqdnDhcpv6SrvTest, processSolicit) {
+    NakedDhcpv6Srv srv(0);
+
+    // Create a Solicit message with FQDN option and generate server's
+    // response using processSolicit function.
+    testProcessMessage(DHCPV6_SOLICIT, "myhost.example.com", srv);
+    EXPECT_TRUE(srv.name_change_reqs_.empty());
+}
+
+// Test that client may send two requests, each carrying FQDN option with
+// a different domain-name. Server should use existing lease for the second
+// request but modify the DNS entries for the lease according to the contents
+// of the FQDN sent in the second request.
+TEST_F(FqdnDhcpv6SrvTest, processTwoRequests) {
+    NakedDhcpv6Srv srv(0);
+
+    // Create a Request message with FQDN option and generate server's
+    // response using processRequest function. This will result in the
+    // creation of a new lease and the appropriate NameChangeRequest
+    // to add both reverse and forward mapping to DNS.
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
+    ASSERT_EQ(1, srv.name_change_reqs_.size());
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                            "2001:db8:1:1::dead:beef",
+                            "000201415AA33D1187D148275136FA30300478"
+                            "FAAAA3EBD29826B5C907B2C9268A6F52",
+                            0, 4000);
+
+    // Client may send another request message with a new domain-name. In this
+    // case the same lease will be returned. The existing DNS entry needs to
+    // be replaced with a new one. Server should determine that the different
+    // FQDN has been already added to the DNS. As a result, the old DNS
+    // entries should be removed and the entries for the new domain-name
+    // should be added. Therefore, we expect two NameChangeRequests. One to
+    // remove the existing entries, one to add new entries.
+    testProcessMessage(DHCPV6_REQUEST, "otherhost.example.com", srv);
+    ASSERT_EQ(2, srv.name_change_reqs_.size());
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+                            "2001:db8:1:1::dead:beef",
+                            "000201415AA33D1187D148275136FA30300478"
+                            "FAAAA3EBD29826B5C907B2C9268A6F52",
+                            0, 4000);
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                            "2001:db8:1:1::dead:beef",
+                            "000201D422AA463306223D269B6CB7AFE7AAD265FC"
+                            "EA97F93623019B2E0D14E5323D5A",
+                            0, 4000);
+
+}
+
+// Test that client may send Request followed by the Renew, both holding
+// FQDN options, but each option holding different domain-name. The Renew
+// should result in generation of the two NameChangeRequests, one to remove
+// DNS entry added previously when Request was processed, another one to
+// add a new entry for the FQDN held in the Renew.
+TEST_F(FqdnDhcpv6SrvTest, processRequestRenew) {
+    NakedDhcpv6Srv srv(0);
+
+    // Create a Request message with FQDN option and generate server's
+    // response using processRequest function. This will result in the
+    // creation of a new lease and the appropriate NameChangeRequest
+    // to add both reverse and forward mapping to DNS.
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
+    ASSERT_EQ(1, srv.name_change_reqs_.size());
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                            "2001:db8:1:1::dead:beef",
+                            "000201415AA33D1187D148275136FA30300478"
+                            "FAAAA3EBD29826B5C907B2C9268A6F52",
+                            0, 4000);
+
+    // Client may send Renew message with a new domain-name. In this
+    // case the same lease will be returned. The existing DNS entry needs to
+    // be replaced with a new one. Server should determine that the different
+    // FQDN has been already added to the DNS. As a result, the old DNS
+    // entries should be removed and the entries for the new domain-name
+    // should be added. Therefore, we expect two NameChangeRequests. One to
+    // remove the existing entries, one to add new entries.
+    testProcessMessage(DHCPV6_RENEW, "otherhost.example.com", srv);
+    ASSERT_EQ(2, srv.name_change_reqs_.size());
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+                            "2001:db8:1:1::dead:beef",
+                            "000201415AA33D1187D148275136FA30300478"
+                            "FAAAA3EBD29826B5C907B2C9268A6F52",
+                            0, 4000);
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                            "2001:db8:1:1::dead:beef",
+                            "000201D422AA463306223D269B6CB7AFE7AAD265FC"
+                            "EA97F93623019B2E0D14E5323D5A",
+                            0, 4000);
+
+}
+
+TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
+    NakedDhcpv6Srv srv(0);
+
+    // Create a Request message with FQDN option and generate server's
+    // response using processRequest function. This will result in the
+    // creation of a new lease and the appropriate NameChangeRequest
+    // to add both reverse and forward mapping to DNS.
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
+    ASSERT_EQ(1, srv.name_change_reqs_.size());
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                            "2001:db8:1:1::dead:beef",
+                            "000201415AA33D1187D148275136FA30300478"
+                            "FAAAA3EBD29826B5C907B2C9268A6F52",
+                            0, 4000);
+
+    // Client may send Release message. In this case the lease should be
+    // removed and all existing DNS entries for this lease should be
+    // also removed. Therefore, we expect that single NameChangeRequest to
+    // remove DNS entries is generated.
+    testProcessMessage(DHCPV6_RELEASE, "otherhost.example.com", srv);
+    ASSERT_EQ(1, srv.name_change_reqs_.size());
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+                            "2001:db8:1:1::dead:beef",
+                            "000201415AA33D1187D148275136FA30300478"
+                            "FAAAA3EBD29826B5C907B2C9268A6F52",
+                            0, 4000);
+
+}
+
+
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// to call processX() methods.
 

+ 4 - 0
src/bin/dhcp6/tests/dhcp6_test_utils.h

@@ -102,11 +102,15 @@ public:
     using Dhcpv6Srv::processRequest;
     using Dhcpv6Srv::processRenew;
     using Dhcpv6Srv::processRelease;
+    using Dhcpv6Srv::processClientFqdn;
+    using Dhcpv6Srv::createNameChangeRequests;
+    using Dhcpv6Srv::createRemovalNameChangeRequest;
     using Dhcpv6Srv::createStatusCode;
     using Dhcpv6Srv::selectSubnet;
     using Dhcpv6Srv::sanityCheck;
     using Dhcpv6Srv::loadServerID;
     using Dhcpv6Srv::writeServerID;
+    using Dhcpv6Srv::name_change_reqs_;
 
     /// @brief packets we pretend to receive
     ///

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

@@ -27,6 +27,7 @@ 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
+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

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

@@ -0,0 +1,455 @@
+// 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>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+#include <util/strutil.h>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Implements the logic for the Option6ClientFqdn class.
+///
+/// The purpose of the class is to separate the implementation details
+/// of the Option6ClientFqdn class from the interface. This implementation
+/// uses b10-libdns classes to process FQDNs. At some point it may be
+/// desired to split b10-libdhcp++ from b10-libdns. In such case the
+/// implementation of this class may be changed. The declaration of the
+/// Option6ClientFqdn class holds the pointer to implementation, so
+/// the transition to a different implementation would not affect the
+/// header file.
+class Option6ClientFqdnImpl {
+public:
+    /// Holds flags carried by the option.
+    uint8_t flags_;
+    /// Holds the pointer to a domain name carried in the option.
+    boost::shared_ptr<isc::dns::Name> domain_name_;
+    /// Indicates whether domain name is partial or fully qualified.
+    Option6ClientFqdn::DomainNameType domain_name_type_;
+
+    /// @brief Constructor, from domain name.
+    ///
+    /// @param flags A value of the flags option field.
+    /// @param domain_name A domain name carried by the option given in the
+    /// textual format.
+    /// @param name_type A value which indicates whether domain-name
+    /// is partial of fully qualified.
+    Option6ClientFqdnImpl(const uint8_t flags,
+                          const std::string& domain_name,
+                          const Option6ClientFqdn::DomainNameType name_type);
+
+    /// @brief Constructor, from wire data.
+    ///
+    /// @param first An iterator pointing to the begining of the option data
+    /// in the wire format.
+    /// @param last An iterator poiting to the end of the option data in the
+    /// wire format.
+    Option6ClientFqdnImpl(OptionBufferConstIter first,
+                          OptionBufferConstIter last);
+
+    /// @brief Copy constructor.
+    ///
+    /// @param source An object being copied.
+    Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source);
+
+    /// @brief Assignment operator.
+    ///
+    /// @param source An object which is being assigned.
+    Option6ClientFqdnImpl& operator=(const Option6ClientFqdnImpl& source);
+
+    /// @brief Set a new domain name for the option.
+    ///
+    /// @param domain_name A new domain name to be assigned.
+    /// @param name_type A value which indicates whether the domain-name is
+    /// partial or fully qualified.
+    void setDomainName(const std::string& domain_name,
+                       const Option6ClientFqdn::DomainNameType name_type);
+
+    /// @brief Check if flags are valid.
+    ///
+    /// In particular, this function checks if the N and S bits are not
+    /// set to 1 in the same time.
+    ///
+    /// @param flags A value carried by the flags field of the option.
+    /// @param check_mbz A boolean value which indicates if this function should
+    /// check if the MBZ bits are set (if true). This parameter should be set
+    /// to false when validating flags in the received message. This is because
+    /// server should ignore MBZ bits in received messages.
+    /// @throw InvalidOption6FqdnFlags if flags are invalid.
+    static void checkFlags(const uint8_t flags, const bool check_mbz);
+
+    /// @brief Parse the Option provided in the wire format.
+    ///
+    /// @param first An iterator pointing to the begining of the option data
+    /// in the wire format.
+    /// @param last An iterator poiting to the end of the option data in the
+    /// wire format.
+    void parseWireData(OptionBufferConstIter first,
+                       OptionBufferConstIter last);
+
+};
+
+Option6ClientFqdnImpl::
+Option6ClientFqdnImpl(const uint8_t flags,
+                      const std::string& domain_name,
+                      // cppcheck 1.57 complains that const enum value is not
+                      // passed by reference. Note that it accepts the non-const
+                      // enum to be passed by value. In both cases it is
+                      // unnecessary to pass the enum by reference.
+                      // cppcheck-suppress passedByValue
+                      const Option6ClientFqdn::DomainNameType name_type)
+    : flags_(flags),
+      domain_name_(),
+      domain_name_type_(name_type) {
+
+    //  Check if flags are correct. Also check if MBZ bits are set.
+    checkFlags(flags_, true);
+    // Set domain name. It may throw an exception if domain name has wrong
+    // format.
+    setDomainName(domain_name, name_type);
+}
+
+Option6ClientFqdnImpl::Option6ClientFqdnImpl(OptionBufferConstIter first,
+                                             OptionBufferConstIter last) {
+    parseWireData(first, last);
+    // Verify that flags value was correct. Do not check if MBZ bits are
+    // set because we should ignore those bits in received message.
+    checkFlags(flags_, false);
+}
+
+Option6ClientFqdnImpl::
+Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source)
+    : flags_(source.flags_),
+      domain_name_(),
+      domain_name_type_(source.domain_name_type_) {
+    if (source.domain_name_) {
+        domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+    }
+}
+
+Option6ClientFqdnImpl&
+// This assignment operator handles assignment to self, it copies all
+// required values.
+// cppcheck-suppress operatorEqToSelf
+Option6ClientFqdnImpl::operator=(const Option6ClientFqdnImpl& source) {
+    if (source.domain_name_) {
+        domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+
+    } else {
+        domain_name_.reset();
+
+    }
+
+    // This assignment should be exception safe.
+    flags_ = source.flags_;
+    domain_name_type_ = source.domain_name_type_;
+
+    return (*this);
+}
+
+void
+Option6ClientFqdnImpl::
+setDomainName(const std::string& domain_name,
+              // cppcheck 1.57 complains that const enum value is not
+              // passed by reference. Note that it accepts the non-const
+              // enum to be passed by value. In both cases it is
+              // unnecessary to pass the enum by reference.
+              // cppcheck-suppress passedByValue
+              const Option6ClientFqdn::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 == Option6ClientFqdn::FULL) {
+            isc_throw(InvalidOption6FqdnDomainName,
+                      "fully qualified domain-name must not be empty"
+                      << " when setting new domain-name for DHCPv6 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, true));
+            domain_name_type_ = name_type;
+
+        } catch (const Exception& ex) {
+            isc_throw(InvalidOption6FqdnDomainName, "invalid domain-name value '"
+                      << domain_name << "' when setting new domain-name for"
+                      << " DHCPv6 Client FQDN Option");
+
+        }
+    }
+}
+
+void
+Option6ClientFqdnImpl::checkFlags(const uint8_t flags, const bool check_mbz) {
+    // The Must Be Zero (MBZ) bits must not be set.
+    if (check_mbz && ((flags & ~Option6ClientFqdn::FLAG_MASK) != 0)) {
+        isc_throw(InvalidOption6FqdnFlags,
+                  "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 & (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S))
+        == (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S)) {
+        isc_throw(InvalidOption6FqdnFlags,
+                  "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");
+    }
+}
+
+void
+Option6ClientFqdnImpl::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) < Option6ClientFqdn::FLAG_FIELD_LEN) {
+        isc_throw(OutOfRange, "DHCPv6 Client FQDN Option ("
+                  << D6O_CLIENT_FQDN << ") is truncated. Minimal option"
+                  << " size is " << Option6ClientFqdn::FLAG_FIELD_LEN
+                  << ", got option with size " << std::distance(first, last));
+    }
+
+    // Parse flags
+    flags_ = *(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());
+            try {
+                domain_name_.reset(new isc::dns::Name(name_buf, true));
+            } catch (const Exception& ex) {
+                isc_throw(InvalidOption6FqdnDomainName, "failed to parse"
+                          "partial domain-name from wire format");
+            }
+            // Terminating zero was missing, so set the domain-name type
+            // to partial.
+            domain_name_type_ = Option6ClientFqdn::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));
+            try {
+                domain_name_.reset(new isc::dns::Name(name_buf, true));
+            } catch (const Exception& ex) {
+                isc_throw(InvalidOption6FqdnDomainName, "failed to parse"
+                          "fully qualified domain-name from wire format");
+            }
+            // Set the domain-type to fully qualified domain name.
+            domain_name_type_ = Option6ClientFqdn::FULL;
+        }
+    }
+}
+
+Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag)
+    : Option(Option::V6, D6O_CLIENT_FQDN),
+      impl_(new Option6ClientFqdnImpl(flag, "", PARTIAL)) {
+}
+
+Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag,
+                                     const std::string& domain_name,
+                                     const DomainNameType domain_name_type)
+    : Option(Option::V6, D6O_CLIENT_FQDN),
+      impl_(new Option6ClientFqdnImpl(flag, domain_name, domain_name_type)) {
+}
+
+Option6ClientFqdn::Option6ClientFqdn(OptionBufferConstIter first,
+                                     OptionBufferConstIter last)
+    : Option(Option::V6, D6O_CLIENT_FQDN, first, last),
+      impl_(new Option6ClientFqdnImpl(first, last)) {
+}
+
+Option6ClientFqdn::~Option6ClientFqdn() {
+    delete(impl_);
+}
+
+Option6ClientFqdn::Option6ClientFqdn(const Option6ClientFqdn& source)
+    : Option(source),
+      impl_(new Option6ClientFqdnImpl(*source.impl_)) {
+}
+
+Option6ClientFqdn&
+// This assignment operator handles assignment to self, it uses copy
+// constructor of Option6ClientFqdnImpl to copy all required values.
+// cppcheck-suppress operatorEqToSelf
+Option6ClientFqdn::operator=(const Option6ClientFqdn& source) {
+    Option6ClientFqdnImpl* old_impl = impl_;
+    impl_ = new Option6ClientFqdnImpl(*source.impl_);
+    delete(old_impl);
+    return (*this);
+}
+
+bool
+Option6ClientFqdn::getFlag(const uint8_t flag) const {
+    // Caller should query for one of the: N, S or O flags. Any other
+    // value is invalid.
+    if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N) {
+        isc_throw(InvalidOption6FqdnFlags, "invalid DHCPv6 Client FQDN"
+                  << " Option flag specified, expected N, S or O");
+    }
+
+    return ((impl_->flags_ & flag) != 0);
+}
+
+void
+Option6ClientFqdn::setFlag(const uint8_t 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. Setting
+    // concurrent bits is discouraged (see header file) but it is not
+    // checked here so it will work.
+    if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) {
+        isc_throw(InvalidOption6FqdnFlags, "invalid DHCPv6 Client FQDN"
+                  << " Option flag " << std::hex
+                  << static_cast<int>(flag) << std::dec
+                  << "is being set. Expected: N, S or 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.
+    Option6ClientFqdnImpl::checkFlags(new_flag, true);
+    impl_->flags_ = new_flag;
+}
+
+void
+Option6ClientFqdn::resetFlags() {
+    impl_->flags_ = 0;
+}
+
+std::string
+Option6ClientFqdn::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
+Option6ClientFqdn::packDomainName(isc::util::OutputBuffer& buf) const {
+    // There is nothing to do if domain-name is empty.
+    if (!impl_->domain_name_) {
+        return;
+    }
+
+    // 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
+Option6ClientFqdn::setDomainName(const std::string& domain_name,
+                                 const DomainNameType domain_name_type) {
+    impl_->setDomainName(domain_name, domain_name_type);
+}
+
+void
+Option6ClientFqdn::resetDomainName() {
+    setDomainName("", PARTIAL);
+}
+
+Option6ClientFqdn::DomainNameType
+Option6ClientFqdn::getDomainNameType() const {
+    return (impl_->domain_name_type_);
+}
+
+void
+Option6ClientFqdn::pack(isc::util::OutputBuffer& buf) {
+    // Header = option code and length.
+    packHeader(buf);
+    // Flags field.
+    buf.writeUint8(impl_->flags_);
+    // Domain name.
+    packDomainName(buf);
+}
+
+void
+Option6ClientFqdn::unpack(OptionBufferConstIter first,
+                          OptionBufferConstIter last) {
+    setData(first, last);
+    impl_->parseWireData(first, last);
+    // Check that the flags in the received option are valid. Ignore MBZ bits
+    // because we don't want to discard the whole option because of MBZ bits
+    // being set.
+    impl_->checkFlags(impl_->flags_, false);
+}
+
+std::string
+Option6ClientFqdn::toText(int indent) {
+    std::ostringstream stream;
+    std::string in(indent, ' '); // base indentation
+    stream << in  << "type=" << type_ << "(CLIENT_FQDN)" << ", "
+           << "flags: ("
+           << "N=" << (getFlag(FLAG_N) ? "1" : "0") << ", "
+           << "O=" << (getFlag(FLAG_O) ? "1" : "0") << ", "
+           << "S=" << (getFlag(FLAG_S) ? "1" : "0") << "), "
+           << "domain-name='" << getDomainName() << "' ("
+           << (getDomainNameType() == PARTIAL ? "partial" : "full")
+           << ")";
+
+    return (stream.str());
+}
+
+uint16_t
+Option6ClientFqdn::len() {
+    uint16_t domain_name_length = 0;
+    if (impl_->domain_name_) {
+        // If domain name is partial, the NULL terminating character
+        // is not included and the option. Length has to be adjusted.
+        domain_name_length = impl_->domain_name_type_ == FULL ?
+            impl_->domain_name_->getLength() :
+            impl_->domain_name_->getLength() - 1;
+    }
+    return (getHeaderLen() + FLAG_FIELD_LEN + domain_name_length);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace

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

@@ -0,0 +1,271 @@
+// 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 InvalidOption6FqdnFlags : public Exception {
+public:
+    InvalidOption6FqdnFlags(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception thrown when invalid domain name is specified.
+class InvalidOption6FqdnDomainName : public Exception {
+public:
+    InvalidOption6FqdnDomainName(const char* file, size_t line,
+                                const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// Forward declaration to implementation of @c Option6ClientFqdn class.
+class Option6ClientFqdnImpl;
+
+/// @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 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 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. 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.
+///
+/// <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 Option6ClientFqdn : public Option {
+public:
+
+    ///
+    ///@name A set of constants setting respective bits in 'flags' field
+    //@{
+    static const uint8_t FLAG_S = 0x01; ///< S bit.
+    static const uint8_t FLAG_O = 0x02; ///< O bit.
+    static const uint8_t FLAG_N = 0x04; ///< N bit.
+    //@}
+
+    /// @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 Type of the domain-name: partial or full.
+    enum DomainNameType {
+        PARTIAL,
+        FULL
+    };
+
+    /// @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 flags a combination of flag bits to be stored in flags field.
+    /// @param domain_name a name to be stored in the domain-name field.
+    /// @param domain_name_type indicates if the domain name is partial
+    /// or full.
+    explicit Option6ClientFqdn(const uint8_t flags,
+                               const std::string& domain_name,
+                               const DomainNameType domain_name_type = FULL);
+
+    /// @brief Constructor, creates option instance using flags.
+    ///
+    /// This constructor creates an instance of the option with empty
+    /// domain-name. This domain-name is marked partial.
+    ///
+    /// @param flags A combination of flag bits to be stored in flags field.
+    Option6ClientFqdn(const uint8_t flags);
+
+    /// @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 Option6ClientFqdn(OptionBufferConstIter first,
+                               OptionBufferConstIter last);
+
+   /// @brief Copy constructor
+    Option6ClientFqdn(const Option6ClientFqdn& source);
+
+    /// @brief Destructor
+    virtual ~Option6ClientFqdn();
+
+    /// @brief Assignment operator
+    Option6ClientFqdn& operator=(const Option6ClientFqdn& source);
+
+    /// @brief Checks if the specified flag of the DHCPv6 Client FQDN %Option
+    /// is set.
+    ///
+    /// This method checks the single bit of flags field. Therefore, a caller
+    /// should use one of the: @c FLAG_S, @c FLAG_N, @c FLAG_O constants as
+    /// an argument of the function. Attempt to use any other value (including
+    /// combinations of these constants) will result in exception.
+    ///
+    /// @param flag A value specifying the flags bit to be checked. It can be
+    /// one of the following: @c FLAG_S, @c FLAG_N, @c FLAG_O.
+    ///
+    /// @return true if the bit of the specified flag is set, false otherwise.
+    bool getFlag(const uint8_t flag) const;
+
+    /// @brief Modifies the value of the specified DHCPv6 Client Fqdn %Option
+    /// flag.
+    ///
+    /// This method sets the single bit of flags field. Therefore, a caller
+    /// should use one of the: @c FLAG_S, @c FLAG_N, @c FLAG_O constants as
+    /// an argument of the function. Attempt to use any other value (including
+    /// combinations of these constants) will result in exception.
+    ///
+    /// @param flag A value specifying the flags bit to be modified. It can
+    /// be one of the following: @c FLAG_S, @c FLAG_N, @c FLAG_O.
+    /// @param set a boolean value which indicates whether flag should be
+    /// set (true), or cleared (false).
+    void setFlag(const uint8_t flag, const bool set);
+
+    /// @brief Sets the flag field value to 0.
+    void resetFlags();
+
+    /// @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 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 A pointer to the implementation.
+    Option6ClientFqdnImpl* impl_;
+};
+
+/// A pointer to the @c Option6ClientFqdn object.
+typedef boost::shared_ptr<Option6ClientFqdn> Option6ClientFqdnPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION6_CLIENT_FQDN_H

+ 7 - 2
src/lib/dhcp/option6_ia.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-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
@@ -16,12 +16,17 @@
 #define OPTION_IA_H
 
 #include <dhcp/option.h>
-
+#include <boost/shared_ptr.hpp>
 #include <stdint.h>
 
 namespace isc {
 namespace dhcp {
 
+class Option6IA;
+
+/// A pointer to the @c Option6IA object.
+typedef boost::shared_ptr<Option6IA> Option6IAPtr;
+
 class Option6IA: public Option {
 
 public:

+ 6 - 0
src/lib/dhcp/option6_iaaddr.h

@@ -17,10 +17,16 @@
 
 #include <asiolink/io_address.h>
 #include <dhcp/option.h>
+#include <boost/shared_ptr.hpp>
 
 namespace isc {
 namespace dhcp {
 
+class Option6IAAddr;
+
+/// A pointer to the @c isc::dhcp::Option6IAAddr object.
+typedef boost::shared_ptr<Option6IAAddr> Option6IAAddrPtr;
+
 class Option6IAAddr: public Option {
 
 public:

+ 3 - 2
src/lib/dhcp/option_data_types.cc

@@ -212,9 +212,10 @@ OptionDataTypeUtil::readFqdn(const std::vector<uint8_t>& buf) {
 
 void
 OptionDataTypeUtil::writeFqdn(const std::string& fqdn,
-                              std::vector<uint8_t>& buf) {
+                              std::vector<uint8_t>& buf,
+                              bool downcase) {
     try {
-        isc::dns::Name name(fqdn);
+        isc::dns::Name name(fqdn, downcase);
         isc::dns::LabelSequence labels(name);
         if (labels.getDataLength() > 0) {
             size_t read_len = 0;

+ 4 - 1
src/lib/dhcp/option_data_types.h

@@ -366,11 +366,14 @@ public:
     ///
     /// @param fqdn fully qualified domain name to be written.
     /// @param [out] buf output buffer.
+    /// @param downcase indicates if the FQDN should be converted to lower
+    /// case (if true). By default it is not converted.
     ///
     /// @throw isc::dhcp::BadDataTypeCast if provided FQDN
     /// is invalid.
     static void writeFqdn(const std::string& fqdn,
-                          std::vector<uint8_t>& buf);
+                          std::vector<uint8_t>& buf,
+                          const bool downcase = false);
 
     /// @brief Read string value from a buffer.
     ///

+ 76 - 37
src/lib/dhcp/option_definition.cc

@@ -19,6 +19,7 @@
 #include <dhcp/option6_addrlst.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_client_fqdn.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_definition.h>
 #include <dhcp/option_int.h>
@@ -103,7 +104,8 @@ OptionDefinition::addRecordField(const OptionDataType data_type) {
     if (data_type >= OPT_RECORD_TYPE ||
         data_type == OPT_ANY_ADDRESS_TYPE ||
         data_type == OPT_EMPTY_TYPE) {
-        isc_throw(isc::BadValue, "attempted to add invalid data type to the record.");
+        isc_throw(isc::BadValue,
+                  "attempted to add invalid data type to the record.");
     }
     record_fields_.push_back(data_type);
 }
@@ -129,19 +131,23 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
                     factoryInteger<int8_t>(u, type, begin, end));
 
         case OPT_UINT16_TYPE:
-            return (array_type_ ? factoryIntegerArray<uint16_t>(u, type, begin, end) :
+            return (array_type_ ?
+                    factoryIntegerArray<uint16_t>(u, type, begin, end) :
                     factoryInteger<uint16_t>(u, type, begin, end));
 
         case OPT_INT16_TYPE:
-            return (array_type_ ? factoryIntegerArray<uint16_t>(u, type, begin, end) :
+            return (array_type_ ?
+                    factoryIntegerArray<uint16_t>(u, type, begin, end) :
                     factoryInteger<int16_t>(u, type, begin, end));
 
         case OPT_UINT32_TYPE:
-            return (array_type_ ? factoryIntegerArray<uint32_t>(u, type, begin, end) :
+            return (array_type_ ?
+                    factoryIntegerArray<uint32_t>(u, type, begin, end) :
                     factoryInteger<uint32_t>(u, type, begin, end));
 
         case OPT_INT32_TYPE:
-            return (array_type_ ? factoryIntegerArray<uint32_t>(u, type, begin, end) :
+            return (array_type_ ?
+                    factoryIntegerArray<uint32_t>(u, type, begin, end) :
                     factoryInteger<int32_t>(u, type, begin, end));
 
         case OPT_IPV4_ADDRESS_TYPE:
@@ -185,6 +191,10 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
                     // option only for the same reasons as described in
                     // for IA_NA and IA_PD above.
                     return (factoryIAAddr6(type, begin, end));
+                } else if (code_ == D6O_CLIENT_FQDN && haveClientFqdnFormat()) {
+                    // FQDN option requires special processing. Thus, there is
+                    // a specialized class to handle it.
+                    return (OptionPtr(new Option6ClientFqdn(begin, end)));
                 }
             } else {
                 if ((code_ == DHO_FQDN) && haveFqdn4Format()) {
@@ -270,10 +280,12 @@ OptionDefinition::validate() const {
             // it no way to tell when other data fields begin.
             err_str << "array of strings is not a valid option definition.";
         } else if (type_ == OPT_BINARY_TYPE) {
-            err_str << "array of binary values is not a valid option definition.";
+            err_str << "array of binary values is not"
+                    << " a valid option definition.";
 
         } else if (type_ == OPT_EMPTY_TYPE) {
-            err_str << "array of empty value is not a valid option definition.";
+            err_str << "array of empty value is not"
+                    << " a valid option definition.";
 
         }
 
@@ -281,33 +293,34 @@ OptionDefinition::validate() const {
         // At least two data fields should be added to the record. Otherwise
         // non-record option definition could be used.
         if (getRecordFields().size() < 2) {
-            err_str << "invalid number of data fields: " << getRecordFields().size()
+            err_str << "invalid number of data fields: "
+                    << getRecordFields().size()
                     << " specified for the option of type 'record'. Expected at"
                     << " least 2 fields.";
 
         } else {
             // If the number of fields is valid we have to check if their order
             // is valid too. We check that string or binary data fields are not
-            // laid before other fields. But we allow that they are laid at the end of
-            // an option.
+            // laid before other fields. But we allow that they are laid at the
+            // end of an option.
             const RecordFieldsCollection& fields = getRecordFields();
             for (RecordFieldsConstIter it = fields.begin();
                  it != fields.end(); ++it) {
                 if (*it == OPT_STRING_TYPE &&
                     it < fields.end() - 1) {
-                    err_str << "string data field can't be laid before data fields"
-                            << " of other types.";
+                    err_str << "string data field can't be laid before data"
+                            << " fields of other types.";
                     break;
                 }
                 if (*it == OPT_BINARY_TYPE &&
                     it < fields.end() - 1) {
-                    err_str << "binary data field can't be laid before data fields"
-                            << " of other types.";
+                    err_str << "binary data field can't be laid before data"
+                            << " fields of other types.";
                 }
                 /// Empty type is not allowed within a record.
                 if (*it == OPT_EMPTY_TYPE) {
-                    err_str << "empty data type can't be stored as a field in an"
-                            << " option record.";
+                    err_str << "empty data type can't be stored as a field in"
+                            << " an option record.";
                     break;
                 }
             }
@@ -357,13 +370,24 @@ OptionDefinition::haveFqdn4Format() const {
             record_fields_[3] == OPT_FQDN_TYPE);
 }
 
+bool
+OptionDefinition::haveClientFqdnFormat() const {
+    return (haveType(OPT_RECORD_TYPE) &&
+            (record_fields_.size() == 2) &&
+            (record_fields_[0] == OPT_UINT8_TYPE) &&
+            (record_fields_[1] == OPT_FQDN_TYPE));
+}
+
 template<typename T>
-T OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str) const {
+T
+OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str)
+    const {
     // Lexical cast in case of our data types make sense only
     // for uintX_t, intX_t and bool type.
     if (!OptionDataTypeTraits<T>::integer_type &&
         OptionDataTypeTraits<T>::type != OPT_BOOLEAN_TYPE) {
-        isc_throw(BadDataTypeCast, "unable to do lexical cast to non-integer and"
+        isc_throw(BadDataTypeCast,
+                  "unable to do lexical cast to non-integer and"
                   << " non-boolean data type");
     }
     // We use the 64-bit value here because it has wider range than
@@ -380,16 +404,18 @@ T OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str) cons
         if (OptionDataTypeTraits<T>::integer_type) {
             data_type_str = "integer";
         }
-        isc_throw(BadDataTypeCast, "unable to do lexical cast to " << data_type_str
-                  << " data type for value " << value_str << ": " << ex.what());
+        isc_throw(BadDataTypeCast, "unable to do lexical cast to "
+                  << data_type_str << " data type for value "
+                  << value_str << ": " << ex.what());
     }
     // Perform range checks for integer values only (exclude bool values).
     if (OptionDataTypeTraits<T>::integer_type) {
         if (result > numeric_limits<T>::max() ||
             result < numeric_limits<T>::min()) {
             isc_throw(BadDataTypeCast, "unable to do lexical cast for value "
-                      << value_str << ". This value is expected to be in the range of "
-                      << numeric_limits<T>::min() << ".." << numeric_limits<T>::max());
+                      << value_str << ". This value is expected to be"
+                      << " in the range of " << numeric_limits<T>::min()
+                      << ".." << numeric_limits<T>::max());
         }
     }
     return (static_cast<T>(result));
@@ -411,30 +437,37 @@ OptionDefinition::writeToBuffer(const std::string& value,
         // That way we actually waste 7 bits but it seems to be the
         // simpler way to encode boolean.
         // @todo Consider if any other encode methods can be used.
-        OptionDataTypeUtil::writeBool(lexicalCastWithRangeCheck<bool>(value), buf);
+        OptionDataTypeUtil::writeBool(lexicalCastWithRangeCheck<bool>(value),
+                                      buf);
         return;
     case OPT_INT8_TYPE:
-        OptionDataTypeUtil::writeInt<uint8_t>(lexicalCastWithRangeCheck<int8_t>(value),
+        OptionDataTypeUtil::writeInt<uint8_t>
+            (lexicalCastWithRangeCheck<int8_t>(value),
                                               buf);
         return;
     case OPT_INT16_TYPE:
-        OptionDataTypeUtil::writeInt<uint16_t>(lexicalCastWithRangeCheck<int16_t>(value),
+        OptionDataTypeUtil::writeInt<uint16_t>
+            (lexicalCastWithRangeCheck<int16_t>(value),
                                                buf);
         return;
     case OPT_INT32_TYPE:
-        OptionDataTypeUtil::writeInt<uint32_t>(lexicalCastWithRangeCheck<int32_t>(value),
+        OptionDataTypeUtil::writeInt<uint32_t>
+            (lexicalCastWithRangeCheck<int32_t>(value),
                                                buf);
         return;
     case OPT_UINT8_TYPE:
-        OptionDataTypeUtil::writeInt<uint8_t>(lexicalCastWithRangeCheck<uint8_t>(value),
+        OptionDataTypeUtil::writeInt<uint8_t>
+            (lexicalCastWithRangeCheck<uint8_t>(value),
                                               buf);
         return;
     case OPT_UINT16_TYPE:
-        OptionDataTypeUtil::writeInt<uint16_t>(lexicalCastWithRangeCheck<uint16_t>(value),
+        OptionDataTypeUtil::writeInt<uint16_t>
+            (lexicalCastWithRangeCheck<uint16_t>(value),
                                                buf);
         return;
     case OPT_UINT32_TYPE:
-        OptionDataTypeUtil::writeInt<uint32_t>(lexicalCastWithRangeCheck<uint32_t>(value),
+        OptionDataTypeUtil::writeInt<uint32_t>
+            (lexicalCastWithRangeCheck<uint32_t>(value),
                                                buf);
         return;
     case OPT_IPV4_ADDRESS_TYPE:
@@ -442,7 +475,8 @@ OptionDefinition::writeToBuffer(const std::string& value,
         {
             asiolink::IOAddress address(value);
             if (!address.isV4() && !address.isV6()) {
-                isc_throw(BadDataTypeCast, "provided address " << address.toText()
+                isc_throw(BadDataTypeCast, "provided address "
+                          << address.toText()
                           << " is not a valid IPv4 or IPv6 address.");
             }
             OptionDataTypeUtil::writeAddress(address, buf);
@@ -470,7 +504,8 @@ OptionPtr
 OptionDefinition::factoryAddrList4(uint16_t type,
                                   OptionBufferConstIter begin,
                                   OptionBufferConstIter end) {
-    boost::shared_ptr<Option4AddrLst> option(new Option4AddrLst(type, begin, end));
+    boost::shared_ptr<Option4AddrLst> option(new Option4AddrLst(type, begin,
+                                                                end));
     return (option);
 }
 
@@ -478,7 +513,8 @@ OptionPtr
 OptionDefinition::factoryAddrList6(uint16_t type,
                                    OptionBufferConstIter begin,
                                    OptionBufferConstIter end) {
-    boost::shared_ptr<Option6AddrLst> option(new Option6AddrLst(type, begin, end));
+    boost::shared_ptr<Option6AddrLst> option(new Option6AddrLst(type, begin,
+                                                                end));
     return (option);
 }
 
@@ -502,8 +538,9 @@ OptionDefinition::factoryIA6(uint16_t type,
                              OptionBufferConstIter begin,
                              OptionBufferConstIter end) {
     if (std::distance(begin, end) < Option6IA::OPTION6_IA_LEN) {
-        isc_throw(isc::OutOfRange, "input option buffer has invalid size, expected "
-                  "at least " << Option6IA::OPTION6_IA_LEN << " bytes");
+        isc_throw(isc::OutOfRange, "input option buffer has invalid size,"
+                  << " expected at least " << Option6IA::OPTION6_IA_LEN
+                  << " bytes");
     }
     boost::shared_ptr<Option6IA> option(new Option6IA(type, begin, end));
     return (option);
@@ -514,10 +551,12 @@ OptionDefinition::factoryIAAddr6(uint16_t type,
                                  OptionBufferConstIter begin,
                                  OptionBufferConstIter end) {
     if (std::distance(begin, end) < Option6IAAddr::OPTION6_IAADDR_LEN) {
-        isc_throw(isc::OutOfRange, "input option buffer has invalid size, expected "
-                  " at least " << Option6IAAddr::OPTION6_IAADDR_LEN << " bytes");
+        isc_throw(isc::OutOfRange,
+                  "input option buffer has invalid size, expected at least "
+                  << Option6IAAddr::OPTION6_IAADDR_LEN << " bytes");
     }
-    boost::shared_ptr<Option6IAAddr> option(new Option6IAAddr(type, begin, end));
+    boost::shared_ptr<Option6IAAddr> option(new Option6IAAddr(type, begin,
+                                                              end));
     return (option);
 }
 

+ 6 - 0
src/lib/dhcp/option_definition.h

@@ -275,6 +275,12 @@ public:
     /// @return true if specified format is IAADDR option format.
     bool haveIAAddr6Format() const;
 
+    /// @brief Check if specified format is OPTION_CLIENT_FQDN option format.
+    ///
+    /// @return true of specified format is OPTION_CLIENT_FQDN option format,
+    /// false otherwise.
+    bool haveClientFqdnFormat() const;
+
     /// @brief Check if option has format of the DHCPv4 Client FQDN
     /// %Option.
     ///

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

@@ -33,6 +33,7 @@ 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_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

+ 3 - 1
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -20,6 +20,7 @@
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option4_client_fqdn.h>
 #include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_client_fqdn.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option_custom.h>
@@ -906,7 +907,8 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
                                     typeid(Option));
 
     LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_FQDN, client_fqdn_buf.begin(),
-                                    client_fqdn_buf.end(), typeid(OptionCustom));
+                                    client_fqdn_buf.end(),
+                                    typeid(Option6ClientFqdn));
 
     LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, begin, end,
                                     typeid(Option6AddrLst));

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

@@ -0,0 +1,809 @@
+// 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;
+
+// This test verifies that constructor accepts empty partial domain-name but
+// does not accept empty fully qualified domain name.
+TEST(Option6ClientFqdnTest, constructEmptyName) {
+    // Create an instance of the source option.
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "",
+                                           Option6ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_TRUE(option->getDomainName().empty());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+
+    // Constructor should not accept empty fully qualified domain name.
+    EXPECT_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "",
+                                   Option6ClientFqdn::FULL),
+                 InvalidOption6FqdnDomainName);
+    // 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(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, " ",
+                                   Option6ClientFqdn::FULL),
+                 InvalidOption6FqdnDomainName);
+
+    // Try different constructor.
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O))
+    );
+    ASSERT_TRUE(option);
+    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_TRUE(option->getDomainName().empty());
+    EXPECT_EQ(Option6ClientFqdn::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(Option6ClientFqdnTest, copyConstruct) {
+    // Create an instance of the source option.
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+                                           "myhost.example.com",
+                                           Option6ClientFqdn::FULL))
+    );
+    ASSERT_TRUE(option);
+
+    // Use copy constructor to create a second instance of the option.
+    boost::scoped_ptr<Option6ClientFqdn> option_copy;
+    ASSERT_NO_THROW(
+        option_copy.reset(new Option6ClientFqdn(*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(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_EQ("myhost.example.com.", option_copy->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::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 Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
+                                           "example",
+                                           Option6ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+
+    // Call copy-constructor to copy the option.
+    ASSERT_NO_THROW(
+        option_copy.reset(new Option6ClientFqdn(*option))
+    );
+    ASSERT_TRUE(option_copy);
+
+    option.reset();
+
+    EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_EQ("example", option_copy->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option_copy->getDomainNameType());
+}
+
+// This test verifies that copy constructor makes a copy of the option, when
+// domain-name is empty.
+TEST(Option6ClientFqdnTest, copyConstructEmptyDomainName) {
+    // Create an instance of the source option.
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S));
+    );
+    ASSERT_TRUE(option);
+
+    // Use copy constructor to create a second instance of the option.
+    boost::scoped_ptr<Option6ClientFqdn> option_copy;
+    ASSERT_NO_THROW(
+        option_copy.reset(new Option6ClientFqdn(*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(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_EQ("", option_copy->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option_copy->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format is parsed correctly.
+TEST(Option6ClientFqdnTest, constructFromWire) {
+    const uint8_t in_data[] = {
+        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 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<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end()))
+    );
+    ASSERT_TRUE(option);
+
+    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_EQ("myhost.example.com.", option->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// Verify that exception is thrown if the domain-name label is
+// longer than 63.
+TEST(Option6ClientFqdnTest, constructFromWireTooLongLabel) {
+    OptionBuffer in_buf(Option6ClientFqdn::FLAG_S);
+    in_buf.push_back(70);
+    in_buf.insert(in_buf.end(), 70, 109);
+    in_buf.push_back(0);
+
+    EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
+                 InvalidOption6FqdnDomainName);
+}
+
+// Verify that exception is thrown if the overall length of the domain-name
+// is over 255.
+TEST(Option6ClientFqdnTest, constructFromWireTooLongDomainName) {
+    OptionBuffer in_buf(Option6ClientFqdn::FLAG_S);
+    for (int i = 0; i < 26;  ++i) {
+        in_buf.push_back(10);
+        in_buf.insert(in_buf.end(), 10, 109);
+    }
+    in_buf.push_back(0);
+
+    try {
+        Option6ClientFqdn(in_buf.begin(), in_buf.end());
+    } catch (const Exception& ex) {
+        std::cout << ex.what() << std::endl;
+    }
+    EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
+                 InvalidOption6FqdnDomainName);
+}
+
+// This test verifies that truncated option is rejected.
+TEST(Option6ClientFqdnTest, constructFromWireTruncated) {
+    // Empty buffer is invalid. It should be at least one octet long.
+    OptionBuffer in_buf;
+    ASSERT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
+                 OutOfRange);
+}
+
+// This test verifies that the option in the on-wire format with partial
+// domain-name is parsed correctly.
+TEST(Option6ClientFqdnTest, constructFromWirePartial) {
+    const uint8_t in_data[] = {
+        Option6ClientFqdn::FLAG_N,           // flags
+        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<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end()))
+    );
+    ASSERT_TRUE(option);
+
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_EQ("myhost", option->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with empty
+// domain-name is parsed correctly.
+TEST(Option6ClientFqdnTest, constructFromWireEmpty) {
+    OptionBuffer in_buf(Option6ClientFqdn::FLAG_S);
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(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(Option6ClientFqdnTest, 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(Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+                                      "myhost.example.com",
+                                      Option6ClientFqdn::FULL));
+
+    ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_N,
+                                      "myhost",
+                                      Option6ClientFqdn::PARTIAL));
+
+    // Create options with the same parameters as tested above.
+
+    // Create first option.
+    Option6ClientFqdn option(Option6ClientFqdn::FLAG_S,
+                             "myhost.example.com",
+                             Option6ClientFqdn::FULL);
+
+    // Verify that the values have been set correctly.
+    ASSERT_TRUE(option.getFlag(Option6ClientFqdn::FLAG_S));
+    ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_O));
+    ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_N));
+    ASSERT_EQ("myhost.example.com.", option.getDomainName());
+    ASSERT_EQ(Option6ClientFqdn::FULL, option.getDomainNameType());
+
+    // Create a second option.
+    Option6ClientFqdn option2(Option6ClientFqdn::FLAG_N,
+                              "myhost",
+                              Option6ClientFqdn::PARTIAL);
+
+    // Verify tha the values have been set correctly.
+    ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_S));
+    ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
+    ASSERT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_N));
+    ASSERT_EQ("myhost", option2.getDomainName());
+    ASSERT_EQ(Option6ClientFqdn::PARTIAL, option2.getDomainNameType());
+
+
+    // Make the assignment.
+    ASSERT_NO_THROW(option2 = option);
+
+    // Both options should now have the same values.
+    EXPECT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_EQ(option.getDomainName(), option2.getDomainName());
+    EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType());
+}
+
+// This test verifies that assignment operator can be used to assign one
+// instance of the option to another, when the domain-name is empty.
+TEST(Option6ClientFqdnTest, assignmentEmptyDomainName) {
+    ASSERT_NO_THROW(
+        Option6ClientFqdn(static_cast<uint8_t>(Option6ClientFqdn::FLAG_S))
+    );
+
+    ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_N,
+                                      "myhost",
+                                      Option6ClientFqdn::PARTIAL));
+
+    // Create options with the same parameters as tested above.
+
+    // Create first option.
+    Option6ClientFqdn option(Option6ClientFqdn::FLAG_S);
+
+    // Verify that the values have been set correctly.
+    ASSERT_TRUE(option.getFlag(Option6ClientFqdn::FLAG_S));
+    ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_O));
+    ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_N));
+    ASSERT_EQ("", option.getDomainName());
+    ASSERT_EQ(Option6ClientFqdn::PARTIAL, option.getDomainNameType());
+
+    // Create a second option.
+    Option6ClientFqdn option2(Option6ClientFqdn::FLAG_N,
+                              "myhost",
+                              Option6ClientFqdn::PARTIAL);
+
+    // Verify that the values have been set correctly.
+    ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_S));
+    ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
+    ASSERT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_N));
+    ASSERT_EQ("myhost", option2.getDomainName());
+    ASSERT_EQ(Option6ClientFqdn::PARTIAL, option2.getDomainNameType());
+
+
+    // Make the assignment.
+    ASSERT_NO_THROW(option2 = option);
+
+    // Both options should now have the same values.
+    EXPECT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_EQ("", 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(Option6ClientFqdnTest, 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(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"),
+                 InvalidOption6FqdnFlags);
+
+    // 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"),
+                 InvalidOption6FqdnFlags);
+}
+
+// This test verifies that constructor which parses option from on-wire format
+// will throw exception if parsed flags field is invalid.
+TEST(Option6ClientFqdnTest, 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(Option6ClientFqdn::FLAG_N);
+    ASSERT_NO_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()));
+
+    // Replace the flags with invalid value and verify that constructor throws
+    // appropriate exception.
+    in_buf[0] = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S;
+    EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
+                 InvalidOption6FqdnFlags);
+}
+
+// This test verifies that if invalid domain name is used the constructor
+// will throw appropriate exception.
+TEST(Option6ClientFqdnTest, 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(Option6ClientFqdn(0, "myhost.example.com"));
+
+    // Specify invalid domain name and expect that exception is thrown.
+    EXPECT_THROW(Option6ClientFqdn(0, "my...host.example.com"),
+                 InvalidOption6FqdnDomainName);
+}
+
+// This test verifies that getFlag throws an exception if flag value other
+// than FLAG_N, FLAG_S, FLAG_O is specified.
+TEST(Option6ClientFqdnTest, getFlag) {
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
+    );
+    ASSERT_TRUE(option);
+
+    // The 0x3 (binary 011) specifies two distinct bits in the flags field.
+    // This value is ambiguous for getFlag function and this function doesn't
+    // know which flag the caller is attempting to check.
+    EXPECT_THROW(option->getFlag(0x3), InvalidOption6FqdnFlags);
+}
+
+// This test verifies that flags can be modified and that incorrect flags
+// are rejected.
+TEST(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),
+                 InvalidOption6FqdnFlags);
+
+    // 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),
+                 InvalidOption6FqdnFlags);
+
+    // 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(flags, true), InvalidOption6FqdnFlags);
+
+    flags = 0x14;
+    ASSERT_THROW(option->setFlag(flags, true), InvalidOption6FqdnFlags);
+}
+
+// This test verifies that flags field of the option is set to 0 when resetFlags
+// function is called.
+TEST(Option6ClientFqdnTest, resetFlags) {
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S |
+                                           Option6ClientFqdn::FLAG_O,
+                                           "myhost.example.com",
+                                           Option6ClientFqdn::FULL))
+    );
+    ASSERT_TRUE(option);
+
+    // Check that flags we set in the constructor are set.
+    ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+    ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+
+    option->resetFlags();
+
+    // After reset, all flags should be 0.
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+}
+
+// This test verifies that current domain-name can be replaced with a new
+// domain-name.
+TEST(Option6ClientFqdnTest, setDomainName) {
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+                                           "myhost.example.com",
+                                           Option6ClientFqdn::FULL))
+    );
+    ASSERT_TRUE(option);
+    ASSERT_EQ("myhost.example.com.", option->getDomainName());
+    ASSERT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+    // Partial domain-name.
+    ASSERT_NO_THROW(option->setDomainName("myhost",
+                                          Option6ClientFqdn::PARTIAL));
+    EXPECT_EQ("myhost", option->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+
+    // Fully qualified domain-name.
+    ASSERT_NO_THROW(option->setDomainName("example.com",
+                                          Option6ClientFqdn::FULL));
+    EXPECT_EQ("example.com.", option->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+    // Empty domain name (partial). This should be successful.
+    ASSERT_NO_THROW(option->setDomainName("", Option6ClientFqdn::PARTIAL));
+    EXPECT_TRUE(option->getDomainName().empty());
+
+    // Fully qualified domain-names must not be empty.
+    EXPECT_THROW(option->setDomainName("", Option6ClientFqdn::FULL),
+                 InvalidOption6FqdnDomainName);
+    EXPECT_THROW(option->setDomainName(" ", Option6ClientFqdn::FULL),
+                 InvalidOption6FqdnDomainName);
+}
+
+// This test verifies that current domain-name can be reset to empty one.
+TEST(Option6ClientFqdnTest, resetDomainName) {
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+                                           "myhost.example.com",
+                                           Option6ClientFqdn::FULL))
+    );
+    ASSERT_TRUE(option);
+    ASSERT_EQ("myhost.example.com.", option->getDomainName());
+    ASSERT_EQ(Option6ClientFqdn::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(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(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 it is possible to encode the option which carries
+// empty domain-name in the wire format.
+TEST(Option6ClientFqdnTest, packEmpty) {
+    // 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))
+    );
+    ASSERT_TRUE(option);
+
+    // Prepare on-wire format of the option.
+    isc::util::OutputBuffer buf(5);
+    ASSERT_NO_THROW(option->pack(buf));
+
+    // Prepare reference data.
+    const uint8_t ref_data[] = {
+        0, 39, 0, 1,                         // header
+        Option6ClientFqdn::FLAG_S            // flags
+    };
+    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(Option6ClientFqdnTest, unpack) {
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
+                                           "myhost",
+                                           Option6ClientFqdn::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(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_EQ("myhost", option->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+
+    const uint8_t in_data[] = {
+        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 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(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_EQ("myhost.example.com.", option->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that on-wire option data holding partial domain name
+// is parsed correctly.
+TEST(Option6ClientFqdnTest, unpackPartial) {
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
+                                           "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(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_EQ("myhost.example.com.", option->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+    const uint8_t in_data[] = {
+        Option6ClientFqdn::FLAG_S,           // flags
+        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(Option6ClientFqdn::FLAG_S));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+    EXPECT_EQ("myhost", option->getDomainName());
+    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the empty buffer is rejected when decoding an option
+// from on-wire format.
+TEST(Option6ClientFqdnTest, unpackTruncated) {
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O))
+    );
+    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(Option6ClientFqdnTest, toText) {
+    // Create option instance. Check that constructor doesn't throw.
+    uint8_t flags = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_O;
+    boost::scoped_ptr<Option6ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(flags,
+                                           "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=39(CLIENT_FQDN), flags: (N=1, O=1, S=0), "
+        "domain-name='myhost.example.com.' (full)";
+    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 Option6ClientFqdn(flags, "myhost",
+                                           Option6ClientFqdn::PARTIAL))
+    );
+    ref_string =
+        "type=39(CLIENT_FQDN), flags: (N=0, O=0, S=0), "
+        "domain-name='myhost' (partial)";
+    EXPECT_EQ(ref_string, option->toText());
+}
+
+// This test verifies that the correct length of the option in on-wire
+// format is returned.
+TEST(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

+ 17 - 2
src/lib/dhcp/tests/option_definition_unittest.cc

@@ -928,7 +928,7 @@ TEST_F(OptionDefinitionTest, utf8StringTokenized) {
     // Let's create some dummy option.
     const uint16_t opt_code = 80;
     OptionDefinition opt_def("OPTION_WITH_STRING", opt_code, "string");
-    
+
     std::vector<std::string> values;
     values.push_back("Hello World");
     values.push_back("this string should not be included in the option");
@@ -960,7 +960,7 @@ TEST_F(OptionDefinitionTest, integerInvalidType) {
 // The purpose of this test is to verify that helper methods
 // haveIA6Format and haveIAAddr6Format can be used to determine
 // IA_NA  and IAADDR option formats.
-TEST_F(OptionDefinitionTest, recognizeFormat) {
+TEST_F(OptionDefinitionTest, haveIAFormat) {
     // IA_NA option format.
     OptionDefinition opt_def1("OPTION_IA_NA", D6O_IA_NA, "record");
     for (int i = 0; i < 3; ++i) {
@@ -984,4 +984,19 @@ TEST_F(OptionDefinitionTest, recognizeFormat) {
     EXPECT_FALSE(opt_def4.haveIAAddr6Format());
 }
 
+// This test verifies that haveClientFqdnFormat function recognizes that option
+// definition describes the format of DHCPv6 Client Fqdn Option Format.
+TEST_F(OptionDefinitionTest, haveClientFqdnFormat) {
+    OptionDefinition opt_def("OPTION_CLIENT_FQDN", D6O_CLIENT_FQDN, "record");
+    opt_def.addRecordField("uint8");
+    opt_def.addRecordField("fqdn");
+    EXPECT_TRUE(opt_def.haveClientFqdnFormat());
+
+    // Create option format which is not matching the Client FQDN option format
+    // to verify that tested function does dont always return true.
+    OptionDefinition opt_def_invalid("OPTION_CLIENT_FQDN", D6O_CLIENT_FQDN,
+                                     "uint8");
+    EXPECT_FALSE(opt_def_invalid.haveClientFqdnFormat());
+}
+
 } // anonymous namespace

+ 5 - 1
src/lib/dhcp_ddns/Makefile.am

@@ -1,7 +1,7 @@
 SUBDIRS = . tests
 
 AM_CPPFLAGS  = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
-AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(BOTAN_INCLUDES)
 AM_CXXFLAGS  = $(B10_CXXFLAGS)
 
 # Some versions of GCC warn about some versions of Boost regarding
@@ -37,11 +37,15 @@ nodist_libb10_dhcp_ddns_la_SOURCES = dhcp_ddns_messages.cc dhcp_ddns_messages.h
 libb10_dhcp_ddns_la_CXXFLAGS = $(AM_CXXFLAGS)
 libb10_dhcp_ddns_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
 libb10_dhcp_ddns_la_LDFLAGS  = $(AM_LDFLAGS)
+libb10_dhcp_ddns_la_LDFLAGS += ${BOTAN_LDFLAGS}
 libb10_dhcp_ddns_la_LIBADD  =
+libb10_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libb10-cryptolink.la
+libb10_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 libb10_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/log/libb10-log.la
 libb10_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la
 libb10_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 libb10_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+libb10_dhcp_ddns_la_LIBADD += ${BOTAN_LIBS} ${BOTAN_RPATH}
 
 if USE_CLANGPP
 # Disable unused parameter warning caused by some of the

+ 63 - 0
src/lib/dhcp_ddns/ncr_msg.cc

@@ -15,6 +15,9 @@
 #include <dhcp_ddns/ncr_msg.h>
 #include <asiolink/io_address.h>
 #include <asiolink/io_error.h>
+#include <cryptolink/cryptolink.h>
+
+#include <botan/sha2_32.h>
 
 #include <sstream>
 #include <limits>
@@ -31,6 +34,12 @@ D2Dhcid::D2Dhcid(const std::string& data) {
     fromStr(data);
 }
 
+D2Dhcid::D2Dhcid(const isc::dhcp::DUID& duid,
+                 const std::vector<uint8_t>& wire_fqdn) {
+    fromDUID(duid, wire_fqdn);
+}
+
+
 void
 D2Dhcid::fromStr(const std::string& data) {
     bytes_.clear();
@@ -44,8 +53,62 @@ D2Dhcid::fromStr(const std::string& data) {
 std::string
 D2Dhcid::toStr() const {
     return (isc::util::encode::encodeHex(bytes_));
+}
+
+void
+D2Dhcid::fromDUID(const isc::dhcp::DUID& duid,
+                  const std::vector<uint8_t>& wire_fqdn) {
+    // DHCID created from DUID starts with two bytes representing
+    // a type of the identifier. The value of 0x0002 indicates that
+    // DHCID has been created from DUID. The 3rd byte is equal to 1
+    // which indicates that the SHA-256 algorithm is used to create
+    // a DHCID digest. This value is called digest-type.
+    static uint8_t dhcid_header[] = { 0x00, 0x02, 0x01 };
+
+    // We get FQDN in the wire format, so we don't know if it is
+    // valid. It is caller's responsibility to make sure it is in
+    // the valid format. Here we just make sure it is not empty.
+    if (wire_fqdn.empty()) {
+        isc_throw(isc::dhcp_ddns::NcrMessageError,
+                  "empty FQDN used to create DHCID");
+    }
+
+    // Get the wire representation of the DUID.
+    std::vector<uint8_t> data = duid.getDuid();
+    // It should be DUID class responsibility to validate the DUID
+    // but let's be on the safe side here and make sure that empty
+    // DUID is not returned.
+    if (data.empty()) {
+        isc_throw(isc::dhcp_ddns::NcrMessageError,
+                  "empty DUID used to create DHCID");
+    }
+
+    // Append FQDN in the wire format.
+    data.insert(data.end(), wire_fqdn.begin(), wire_fqdn.end());
 
+    // Use the DUID and FQDN to compute the digest (see RFC4701, section 3).
+
+    // The getCryptoLink is a common function to initialize the Botan library.
+    cryptolink::CryptoLink::getCryptoLink();
+    // @todo The code below, which calculates the SHA-256 may need to be moved
+    // to the cryptolink library.
+    Botan::SecureVector<Botan::byte> secure;
+    try {
+        Botan::SHA_256 sha;
+        // We have checked already that the DUID and FQDN aren't empty
+        // so it is safe to assume that the data buffer is not empty.
+        secure = sha.process(static_cast<const Botan::byte*>(&data[0]),
+                             data.size());
+    } catch (const std::exception& ex) {
+        isc_throw(isc::dhcp_ddns::NcrMessageError,
+                  "error while generating DHCID from DUID: "
+                  << ex.what());
+    }
 
+    // The exception unsafe part is finished, so we can finally replace
+    // the contents of bytes_.
+    bytes_.assign(dhcid_header, dhcid_header + sizeof(dhcid_header));
+    bytes_.insert(bytes_.end(), secure.begin(), secure.end());
 }
 
 

+ 20 - 0
src/lib/dhcp_ddns/ncr_msg.h

@@ -21,6 +21,7 @@
 /// DHCP-DDNS. These requests are referred to as NameChangeRequests.
 
 #include <cc/data.h>
+#include <dhcp/duid.h>
 #include <exceptions/exceptions.h>
 #include <util/buffer.h>
 #include <util/encode/hex.h>
@@ -77,6 +78,14 @@ public:
     /// or there is an odd number of digits.
     D2Dhcid(const std::string& data);
 
+    /// @brief Constructor, creates an instance of the @c D2Dhcid from the
+    /// @c isc::dhcp::DUID.
+    ///
+    /// @param duid An object representing DUID.
+    /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+    D2Dhcid(const isc::dhcp::DUID& duid,
+            const std::vector<uint8_t>& wire_fqdn);
+
     /// @brief Returns the DHCID value as a string of hexadecimal digits.
     ///
     /// @return a string containing a contiguous stream of digits.
@@ -92,6 +101,17 @@ public:
     /// or there is an odd number of digits.
     void fromStr(const std::string& data);
 
+    /// @brief Sets the DHCID value based on the DUID and FQDN.
+    ///
+    /// This function requires that the FQDN conforms to the section 3.5
+    /// of the RFC4701, which says that the FQDN must be in lowercase.
+    /// This function doesn't validate if it really converted.
+    ///
+    /// @param duid A @c isc::dhcp::DUID object encapsulating DUID.
+    /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+    void fromDUID(const isc::dhcp::DUID& duid,
+                  const std::vector<uint8_t>& wire_fqdn);
+
     /// @brief Returns a reference to the DHCID byte vector.
     ///
     /// @return a reference to the vector.

+ 5 - 6
src/lib/dhcp_ddns/tests/Makefile.am

@@ -1,7 +1,7 @@
 SUBDIRS = .
 
 AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
-AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(BOTAN_INCLUDES)
 AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcp_ddns/tests\"
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 
@@ -27,13 +27,8 @@ if HAVE_GTEST
 TESTS += libdhcp_ddns_unittests
 
 libdhcp_ddns_unittests_SOURCES  = run_unittests.cc
-libdhcp_ddns_unittests_SOURCES += ../dhcp_ddns_log.cc ../dhcp_ddns_log.h
-libdhcp_ddns_unittests_SOURCES += ../ncr_io.cc ../ncr_io.h
-libdhcp_ddns_unittests_SOURCES += ../ncr_msg.cc ../ncr_msg.h
-libdhcp_ddns_unittests_SOURCES += ../ncr_udp.cc ../ncr_udp.h
 libdhcp_ddns_unittests_SOURCES += ncr_unittests.cc
 libdhcp_ddns_unittests_SOURCES += ncr_udp_unittests.cc
-nodist_libdhcp_ddns_unittests_SOURCES = ../dhcp_ddns_messages.h ../dhcp_ddns_messages.cc
 
 libdhcp_ddns_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
 
@@ -50,9 +45,13 @@ endif
 libdhcp_ddns_unittests_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
 libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libb10-cryptolink.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
 libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+libdhcp_ddns_unittests_LDADD += ${BOTAN_LIBS} ${BOTAN_RPATH}
 libdhcp_ddns_unittests_LDADD += $(GTEST_LDADD)
 endif
 

+ 100 - 0
src/lib/dhcp_ddns/tests/ncr_unittests.cc

@@ -13,6 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <dhcp_ddns/ncr_msg.h>
+#include <dhcp/duid.h>
 #include <util/time_utilities.h>
 
 #include <gtest/gtest.h>
@@ -21,6 +22,7 @@
 using namespace std;
 using namespace isc;
 using namespace isc::dhcp_ddns;
+using namespace isc::dhcp;
 
 namespace {
 
@@ -288,6 +290,104 @@ TEST(NameChangeRequestTest, dhcidTest) {
 
 }
 
+/// Tests that DHCID is correctly created from a DUID and FQDN. The final format
+/// of the DHCID is as follows:
+/// <identifier-type> <digest-type-code> <digest>
+/// where:
+/// - identifier-type (2 octets) is 0x0002.
+/// - digest-type-code (1 octet) indicates SHA-256 hashing and is equal 0x1.
+/// - digest = SHA-256(<DUID> <FQDN>)
+/// Note: FQDN is given in the on-wire canonical format.
+TEST(NameChangeRequestTest, dhcidFromDUID) {
+    D2Dhcid dhcid;
+
+    // Create DUID.
+    uint8_t duid_data[] = { 0, 1, 2, 3, 4, 5, 6 };
+    DUID duid(duid_data, sizeof(duid_data));
+
+    // Create FQDN in on-wire format: myhost.example.com. It is encoded
+    // as a set of labels, each preceded by its length. The whole FQDN
+    // is zero-terminated.
+    const uint8_t fqdn_data[] = {
+        6, 109, 121, 104, 111, 115, 116,     // myhost.
+        7, 101, 120, 97, 109, 112, 108, 101, // example.
+        3, 99, 111, 109, 0                   // com.
+    };
+    std::vector<uint8_t> wire_fqdn(fqdn_data, fqdn_data + sizeof(fqdn_data));
+
+    // Create DHCID.
+    ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn));
+
+    // The reference DHCID (represented as string of hexadecimal digits)
+    // has been calculated using one of the online calculators.
+    std::string dhcid_ref = "0002012191B7B21AF97E0E656DF887C5E2D"
+        "EF30E7758A207EDF4CCB2DE8CA37066021C";
+
+    // Make sure that the DHCID is valid.
+    EXPECT_EQ(dhcid_ref, dhcid.toStr());
+}
+
+// Test that DHCID is correctly created when the DUID has minimal length (1).
+TEST(NameChangeRequestTest, dhcidFromMinDUID) {
+    D2Dhcid dhcid;
+
+    // Create DUID.
+    uint8_t duid_data[] = { 1 };
+    DUID duid(duid_data, sizeof(duid_data));
+
+    // Create FQDN in on-wire format: myhost.example.com. It is encoded
+    // as a set of labels, each preceded by its length. The whole FQDN
+    // is zero-terminated.
+    const uint8_t fqdn_data[] = {
+        6, 109, 121, 104, 111, 115, 116,     // myhost.
+        7, 101, 120, 97, 109, 112, 108, 101, // example.
+        3, 99, 111, 109, 0                   // com.
+    };
+    std::vector<uint8_t> wire_fqdn(fqdn_data, fqdn_data + sizeof(fqdn_data));
+
+    // Create DHCID.
+    ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn));
+
+    // The reference DHCID (represented as string of hexadecimal digits)
+    // has been calculated using one of the online calculators.
+    std::string dhcid_ref = "000201F89004F73E60CAEDFF514E11CB91D"
+        "1F45C8F0A55D4BC4C688484A819F8EA4074";
+
+    // Make sure that the DHCID is valid.
+    EXPECT_EQ(dhcid_ref, dhcid.toStr());
+}
+
+// Test that DHCID is correctly created when the DUID has maximal length (128).
+TEST(NameChangeRequestTest, dhcidFromMaxDUID) {
+    D2Dhcid dhcid;
+
+    // Create DUID.
+    std::vector<uint8_t> duid_data(128, 1);
+    DUID duid(&duid_data[0], duid_data.size());
+
+    // Create FQDN in on-wire format: myhost.example.com. It is encoded
+    // as a set of labels, each preceded by its length. The whole FQDN
+    // is zero-terminated.
+    const uint8_t fqdn_data[] = {
+        6, 109, 121, 104, 111, 115, 116,     // myhost.
+        7, 101, 120, 97, 109, 112, 108, 101, // example.
+        3, 99, 111, 109, 0                   // com.
+    };
+    std::vector<uint8_t> wire_fqdn(fqdn_data, fqdn_data + sizeof(fqdn_data));
+
+    // Create DHCID.
+    ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn));
+
+    // The reference DHCID (represented as string of hexadecimal digits)
+    // has been calculated using one of the online calculators.
+    std::string dhcid_ref = "00020137D8FBDC0585B44DFA03FAD2E36C6"
+        "159737D545A12EFB40B0D88D110A5748234";
+
+    // Make sure that the DHCID is valid.
+    EXPECT_EQ(dhcid_ref, dhcid.toStr());
+}
+
+
 /// @brief Verifies the fundamentals of converting from and to JSON.
 /// It verifies that:
 /// 1. A NameChangeRequest can be created from a valid JSON string.

+ 29 - 6
src/lib/dhcpsrv/alloc_engine.cc

@@ -198,6 +198,9 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
                               const DuidPtr& duid,
                               uint32_t iaid,
                               const IOAddress& hint,
+                              const bool fwd_dns_update,
+                              const bool rev_dns_update,
+                              const std::string& hostname,
                               bool fake_allocation,
                               const isc::hooks::CalloutHandlePtr& callout_handle) {
 
@@ -232,7 +235,11 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
                 /// implemented
 
                 // the hint is valid and not currently used, let's create a lease for it
-                Lease6Ptr lease = createLease6(subnet, duid, iaid, hint, callout_handle,
+                Lease6Ptr lease = createLease6(subnet, duid, iaid,
+                                               hint,
+                                               fwd_dns_update,
+                                               rev_dns_update, hostname,
+                                               callout_handle,
                                                fake_allocation);
 
                 // It can happen that the lease allocation failed (we could have lost
@@ -244,7 +251,9 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
             } else {
                 if (existing->expired()) {
                     return (reuseExpiredLease(existing, subnet, duid, iaid,
-                                              callout_handle, fake_allocation));
+                                              fwd_dns_update, rev_dns_update,
+                                              hostname, callout_handle,
+                                              fake_allocation));
                 }
 
             }
@@ -278,6 +287,8 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
                 // there's no existing lease for selected candidate, so it is
                 // free. Let's allocate it.
                 Lease6Ptr lease = createLease6(subnet, duid, iaid, candidate,
+                                               fwd_dns_update, rev_dns_update,
+                                               hostname,
                                                callout_handle, fake_allocation);
                 if (lease) {
                     return (lease);
@@ -289,7 +300,9 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
             } else {
                 if (existing->expired()) {
                     return (reuseExpiredLease(existing, subnet, duid, iaid,
-                                              callout_handle, fake_allocation));
+                                              fwd_dns_update, rev_dns_update,
+                                              hostname, callout_handle,
+                                              fake_allocation));
                 }
             }
 
@@ -472,6 +485,9 @@ Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
                                          const Subnet6Ptr& subnet,
                                          const DuidPtr& duid,
                                          uint32_t iaid,
+                                         const bool fwd_dns_update,
+                                         const bool rev_dns_update,
+                                         const std::string& hostname,
                                          const isc::hooks::CalloutHandlePtr& callout_handle,
                                          bool fake_allocation /*= false */ ) {
 
@@ -489,9 +505,9 @@ Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
     expired->cltt_ = time(NULL);
     expired->subnet_id_ = subnet->getID();
     expired->fixed_ = false;
-    expired->hostname_ = std::string("");
-    expired->fqdn_fwd_ = false;
-    expired->fqdn_rev_ = false;
+    expired->hostname_ = hostname;
+    expired->fqdn_fwd_ = fwd_dns_update;
+    expired->fqdn_rev_ = rev_dns_update;
 
     /// @todo: log here that the lease was reused (there's ticket #2524 for
     /// logging in libdhcpsrv)
@@ -624,6 +640,9 @@ Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
                                     const DuidPtr& duid,
                                     uint32_t iaid,
                                     const IOAddress& addr,
+                                    const bool fwd_dns_update,
+                                    const bool rev_dns_update,
+                                    const std::string& hostname,
                                     const isc::hooks::CalloutHandlePtr& callout_handle,
                                     bool fake_allocation /*= false */ ) {
 
@@ -631,6 +650,10 @@ Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
                                subnet->getPreferred(), subnet->getValid(),
                                subnet->getT1(), subnet->getT2(), subnet->getID()));
 
+    lease->fqdn_fwd_ = fwd_dns_update;
+    lease->fqdn_rev_ = rev_dns_update;
+    lease->hostname_ = hostname;
+
     // Let's execute all callouts registered for lease6_select
     if (callout_handle &&
         HooksManager::getHooksManager().calloutsPresent(hook_index_lease6_select_)) {

+ 26 - 2
src/lib/dhcpsrv/alloc_engine.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-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
@@ -237,6 +237,11 @@ protected:
     /// @param duid Client's DUID
     /// @param iaid iaid field from the IA_NA container that client sent
     /// @param hint a hint that the client provided
+    /// @param fwd_dns_update A boolean value which indicates that server takes
+    /// responisibility for the forward DNS Update for this lease (if true).
+    /// @param rev_dns_update A boolean value which indicates that server takes
+    /// responibility for the reverse DNS Update for this lease (if true).
+    /// @param hostname A fully qualified domain-name of the client.
     /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
     ///        an address for SOLICIT that is not really allocated (true)
     /// @param callout_handle a callout handle (used in hooks). A lease callouts
@@ -248,6 +253,9 @@ protected:
                      const DuidPtr& duid,
                      uint32_t iaid,
                      const isc::asiolink::IOAddress& hint,
+                     const bool fwd_dns_update,
+                     const bool rev_dns_update,
+                     const std::string& hostname,
                      bool fake_allocation,
                      const isc::hooks::CalloutHandlePtr& callout_handle);
 
@@ -287,7 +295,13 @@ private:
     /// @param subnet subnet the lease is allocated from
     /// @param duid client's DUID
     /// @param iaid IAID from the IA_NA container the client sent to us
-    /// @param addr an address that was selected and is confirmed to be available
+    /// @param addr an address that was selected and is confirmed to be
+    /// available
+    /// @param fwd_dns_update A boolean value which indicates that server takes
+    /// responisibility for the forward DNS Update for this lease (if true).
+    /// @param rev_dns_update A boolean value which indicates that server takes
+    /// responibility for the reverse DNS Update for this lease (if true).
+    /// @param hostname A fully qualified domain-name of the client.
     /// @param callout_handle a callout handle (used in hooks). A lease callouts
     ///        will be executed if this parameter is passed (and there are callouts
     ///        registered)
@@ -297,6 +311,8 @@ private:
     ///        becomed unavailable)
     Lease6Ptr createLease6(const Subnet6Ptr& subnet, const DuidPtr& duid,
                            uint32_t iaid, const isc::asiolink::IOAddress& addr,
+                           const bool fwd_dns_update, const bool rev_dns_update,
+                           const std::string& hostname,
                            const isc::hooks::CalloutHandlePtr& callout_handle,
                            bool fake_allocation = false);
 
@@ -332,6 +348,11 @@ private:
     /// @param subnet subnet the lease is allocated from
     /// @param duid client's DUID
     /// @param iaid IAID from the IA_NA container the client sent to us
+    /// @param fwd_dns_update A boolean value which indicates that server takes
+    /// responisibility for the forward DNS Update for this lease (if true).
+    /// @param rev_dns_update A boolean value which indicates that server takes
+    /// responibility for the reverse DNS Update for this lease (if true).
+    /// @param hostname A fully qualified domain-name of the client.
     /// @param callout_handle a callout handle (used in hooks). A lease callouts
     ///        will be executed if this parameter is passed.
     /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
@@ -340,6 +361,9 @@ private:
     /// @throw BadValue if trying to recycle lease that is still valid
     Lease6Ptr reuseExpiredLease(Lease6Ptr& expired, const Subnet6Ptr& subnet,
                                 const DuidPtr& duid, uint32_t iaid,
+                                const bool fwd_dns_update,
+                                const bool rev_dns_update,
+                                const std::string& hostname,
                                 const isc::hooks::CalloutHandlePtr& callout_handle,
                                 bool fake_allocation = false);
 

+ 30 - 9
src/lib/dhcpsrv/tests/alloc_engine_unittest.cc

@@ -208,7 +208,9 @@ TEST_F(AllocEngine6Test, simpleAlloc6) {
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
     ASSERT_TRUE(engine);
 
-    Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
+    Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
+                                               IOAddress("::"), false,
+                                               false, "",
                                                false, CalloutHandlePtr());
 
     // Check that we got a lease
@@ -231,8 +233,10 @@ TEST_F(AllocEngine6Test, fakeAlloc6) {
     ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
     ASSERT_TRUE(engine);
 
-    Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
-                                               true, CalloutHandlePtr());
+    Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
+                                               IOAddress("::"), false,
+                                               false, "", true,
+                                               CalloutHandlePtr());
 
     // Check that we got a lease
     ASSERT_TRUE(lease);
@@ -254,6 +258,7 @@ TEST_F(AllocEngine6Test, allocWithValidHint6) {
 
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
                                                IOAddress("2001:db8:1::15"),
+                                               false, false, "",
                                                false, CalloutHandlePtr());
 
     // Check that we got a lease
@@ -292,6 +297,7 @@ TEST_F(AllocEngine6Test, allocWithUsedHint6) {
     // twice.
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
                                                IOAddress("2001:db8:1::1f"),
+                                               false, false, "",
                                                false, CalloutHandlePtr());
     // Check that we got a lease
     ASSERT_TRUE(lease);
@@ -325,6 +331,7 @@ TEST_F(AllocEngine6Test, allocBogusHint6) {
     // with the normal allocation
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
                                                IOAddress("3000::abc"),
+                                               false, false, "",
                                                false, CalloutHandlePtr());
     // Check that we got a lease
     ASSERT_TRUE(lease);
@@ -351,12 +358,16 @@ TEST_F(AllocEngine6Test, allocateAddress6Nulls) {
 
     // Allocations without subnet are not allowed
     Lease6Ptr lease = engine->allocateAddress6(Subnet6Ptr(), duid_, iaid_,
-                                               IOAddress("::"), false, CalloutHandlePtr());
+                                               IOAddress("::"),
+                                               false, false, "", false,
+                                               CalloutHandlePtr());
     ASSERT_FALSE(lease);
 
     // Allocations without DUID are not allowed either
     lease = engine->allocateAddress6(subnet_, DuidPtr(), iaid_,
-                                     IOAddress("::"), false, CalloutHandlePtr());
+                                     IOAddress("::"),
+                                     false, false, "", false,
+                                     CalloutHandlePtr());
     ASSERT_FALSE(lease);
 }
 
@@ -444,7 +455,9 @@ TEST_F(AllocEngine6Test, smallPool6) {
     subnet_->addPool(pool_);
     cfg_mgr.addSubnet6(subnet_);
 
-    Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
+    Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
+                                               IOAddress("::"),
+                                               false, false, "",
                                                false, CalloutHandlePtr());
 
     // Check that we got that single lease
@@ -491,7 +504,9 @@ TEST_F(AllocEngine6Test, outOfAddresses6) {
     // There is just a single address in the pool and allocated it to someone
     // else, so the allocation should fail
     Lease6Ptr lease2 = engine->allocateAddress6(subnet_, duid_, iaid_,
-                                                IOAddress("::"), false, CalloutHandlePtr());
+                                                IOAddress("::"),
+                                                false, false, "", false,
+                                                CalloutHandlePtr());
     EXPECT_FALSE(lease2);
 }
 
@@ -525,6 +540,7 @@ TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
 
     // CASE 1: Asking for any address
     lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
+                                     false, false, "",
                                      true, CalloutHandlePtr());
     // Check that we got that single lease
     ASSERT_TRUE(lease);
@@ -534,7 +550,9 @@ TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
     checkLease6(lease);
 
     // CASE 2: Asking specifically for this address
-    lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress(addr.toText()),
+    lease = engine->allocateAddress6(subnet_, duid_, iaid_,
+                                     IOAddress(addr.toText()),
+                                     false, false, "",
                                      true, CalloutHandlePtr());
     // Check that we got that single lease
     ASSERT_TRUE(lease);
@@ -569,7 +587,8 @@ TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
 
     // A client comes along, asking specifically for this address
     lease = engine->allocateAddress6(subnet_, duid_, iaid_,
-                                     IOAddress(addr.toText()), false,
+                                     IOAddress(addr.toText()),
+                                     false, false, "", false,
                                      CalloutHandlePtr());
 
     // Check that he got that single lease
@@ -1161,6 +1180,7 @@ TEST_F(HookAllocEngine6Test, lease6_select) {
     CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
 
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
+                                               false, false, "",
                                                false, callout_handle);
     // Check that we got a lease
     ASSERT_TRUE(lease);
@@ -1229,6 +1249,7 @@ TEST_F(HookAllocEngine6Test, change_lease6_select) {
 
     // Call allocateAddress6. Callouts should be triggered here.
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
+                                               false, false, "",
                                                false, callout_handle);
     // Check that we got a lease
     ASSERT_TRUE(lease);