Commit 4e902ba3 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[3036] Address further review comments.

parent 3edad954
......@@ -56,6 +56,7 @@
* - @subpage dhcpv6Session
* - @subpage dhcpv6ConfigParser
* - @subpage dhcpv6ConfigInherit
* - @subpage dhcpv6DDNSIntegration
* - @subpage dhcpv6Other
* - @subpage libdhcp
* - @subpage libdhcpIntro
......
......@@ -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>
......
// Copyright (C) 2013 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
......@@ -93,21 +93,21 @@
@section dhcpv6DDNSIntegration DHCPv6 Server Support for the Dynamic DNS Updates
The DHCPv6 server supports processing of the DHCPv6 Client FQDN Option described in
the RFC4307. This Option is sent by the DHCPv6 client to instruct the server to
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 RFC4307, client may choose that the server delegates the forward
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 bind10-d2 process is responsible for the actual communication with the DNS
server, i.e. to send DNS Update messages. The bind10-dhcp6 module is responsible
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
bind10-d2 module. The @ref isc::dhcp_ddns::NameChangeRequest object represents changes 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
......@@ -116,24 +116,25 @@ 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-d2 module when server is idle waiting for DHCP messages.
send them to the bind10-dhcp-ddns module when server is idle waiting for DHCP messages.
Depending on the message type, 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.
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-d2 module should add a new DNS
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-d2 module that the existing DNS binding should be removed
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, 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.
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
......@@ -147,14 +148,12 @@ received from the client. If the FQDN sent in the message which triggered a rene
doesn't change (comparing to the information in the lease database) the NameChangeRequest
is not generated.
@todo The decision about not generating the NameChangeRequest for the client which
renews the lease but doesn't change its FQDN may be wrong in case it is necessary
to inform the bind10-d2 module that the lease has been extended. However, the
house keeper process (which removes DNS bindings for expired leases) will be
implemented within the bind10-dhcp6 module (not the bind10-d2), so there is no
need to store lease lifetime information in the bind10-d2 and thus send it there.
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 is comprises "NOS" flags which communicate to the
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
......
......@@ -1046,6 +1046,8 @@ Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
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.
......@@ -1094,11 +1096,13 @@ Dhcpv6Srv::createRemovalNameChangeRequest(const Lease6Ptr& lease) {
// 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.
// 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);
OptionDataTypeUtil::writeFqdn(lease->hostname_, hostname_wire, true);
} catch (const Exception& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_DDNS_REMOVE_INVALID_HOSTNAME);
return;
......
......@@ -352,6 +352,8 @@ protected:
/// 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.
......
......@@ -58,12 +58,11 @@ using namespace std;
// Maybe it should be isc::test?
namespace {
const uint8_t FQDN_FLAG_S = 0x1;
const uint8_t FQDN_FLAG_O = 0x2;
const uint8_t FQDN_FLAG_N = 0x4;
// 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_.
......@@ -74,9 +73,11 @@ public:
}
// 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,
......@@ -202,12 +203,9 @@ public:
ASSERT_NO_THROW(answ_fqdn = srv.processClientFqdn(question));
ASSERT_TRUE(answ_fqdn);
const bool flag_n = (exp_flags & Option6ClientFqdn::FLAG_N) != 0 ?
true : false;
const bool flag_s = (exp_flags & Option6ClientFqdn::FLAG_S) != 0 ?
true : false;
const bool flag_o = (exp_flags & Option6ClientFqdn::FLAG_O) != 0 ?
true : false;
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));
......@@ -225,7 +223,7 @@ public:
// Create a message of a specified type, add server id and
// FQDN option.
OptionPtr srvid = srv.getServerID();
Pkt6Ptr req = generatePktWithFqdn(msg_type, FQDN_FLAG_S,
Pkt6Ptr req = generatePktWithFqdn(msg_type, Option6ClientFqdn::FLAG_S,
hostname,
Option6ClientFqdn::FULL,
true, srvid);
......@@ -298,6 +296,7 @@ public:
srv.name_change_reqs_.pop();
}
// Holds a lease used by a test.
Lease6Ptr lease_;
};
......@@ -1736,7 +1735,8 @@ TEST_F(Dhcpv6SrvTest, ServerID) {
// Test server's response when client requests that server performs AAAA update.
TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdate) {
testFqdn(DHCPV6_SOLICIT, true, FQDN_FLAG_S, "myhost.example.com",
testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_S,
"myhost.example.com",
Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_S,
"myhost.example.com.");
}
......@@ -1744,7 +1744,7 @@ TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdate) {
// 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, FQDN_FLAG_S, "myhost",
testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_S, "myhost",
Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S,
"myhost.example.com.");
}
......@@ -1752,14 +1752,15 @@ TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdatePartialName) {
// 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, FQDN_FLAG_S, "",
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, FQDN_FLAG_N, "myhost.example.com",
testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_N,
"myhost.example.com",
Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_N,
"myhost.example.com.");
}
......@@ -1768,7 +1769,8 @@ TEST_F(FqdnDhcpv6SrvTest, noUpdate) {
// update to the client and this delegation is not allowed.
TEST_F(FqdnDhcpv6SrvTest, clientAAAAUpdateNotAllowed) {
testFqdn(DHCPV6_SOLICIT, true, 0, "myhost.example.com.",
Option6ClientFqdn::FULL, FQDN_FLAG_S | FQDN_FLAG_O,
Option6ClientFqdn::FULL,
Option6ClientFqdn::FLAG_S | Option6ClientFqdn::FLAG_O,
"myhost.example.com.");
}
......@@ -1974,9 +1976,7 @@ TEST_F(FqdnDhcpv6SrvTest, processSolicit) {
NakedDhcpv6Srv srv(0);
// Create a Solicit 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.
// response using processSolicit function.
testProcessMessage(DHCPV6_SOLICIT, "myhost.example.com", srv);
EXPECT_TRUE(srv.name_change_reqs_.empty());
}
......
......@@ -90,7 +90,7 @@ public:
/// 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 InvalidFqdnOptionFlags if flags are invalid.
/// @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.
......@@ -139,7 +139,13 @@ Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source)
Option6ClientFqdnImpl&
Option6ClientFqdnImpl::operator=(const Option6ClientFqdnImpl& source) {
domain_name_.reset(new isc::dns::Name(*source.domain_name_));
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_;
......@@ -157,7 +163,7 @@ setDomainName(const std::string& domain_name,
std::string name = isc::util::str::trim(domain_name);
if (name.empty()) {
if (name_type == Option6ClientFqdn::FULL) {
isc_throw(InvalidFqdnOptionDomainName,
isc_throw(InvalidOption6FqdnDomainName,
"fully qualified domain-name must not be empty"
<< " when setting new domain-name for DHCPv6 Client"
<< " FQDN Option");
......@@ -172,7 +178,7 @@ setDomainName(const std::string& domain_name,
domain_name_type_ = name_type;
} catch (const Exception& ex) {
isc_throw(InvalidFqdnOptionDomainName, "invalid domain-name value '"
isc_throw(InvalidOption6FqdnDomainName, "invalid domain-name value '"
<< domain_name << "' when setting new domain-name for"
<< " DHCPv6 Client FQDN Option");
......@@ -184,7 +190,7 @@ 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(InvalidFqdnOptionFlags,
isc_throw(InvalidOption6FqdnFlags,
"invalid DHCPv6 Client FQDN Option flags: 0x"
<< std::hex << static_cast<int>(flags) << std::dec);
}
......@@ -193,7 +199,7 @@ Option6ClientFqdnImpl::checkFlags(const uint8_t flags, const bool check_mbz) {
// MUST be 0. Checking it here.
if ((flags & (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S))
== (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S)) {
isc_throw(InvalidFqdnOptionFlags,
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");
......@@ -227,7 +233,12 @@ Option6ClientFqdnImpl::parseWireData(OptionBufferConstIter first,
buf.push_back(0);
// Reset domain name.
isc::util::InputBuffer name_buf(&buf[0], buf.size());
domain_name_.reset(new isc::dns::Name(name_buf));
try {
domain_name_.reset(new isc::dns::Name(name_buf));
} 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;
......@@ -237,7 +248,12 @@ Option6ClientFqdnImpl::parseWireData(OptionBufferConstIter first,
// Name object constructor.
isc::util::InputBuffer name_buf(&(*first),
std::distance(first, last));
domain_name_.reset(new isc::dns::Name(name_buf));
try {
domain_name_.reset(new isc::dns::Name(name_buf));
} 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;
}
......@@ -280,15 +296,11 @@ Option6ClientFqdn::operator=(const Option6ClientFqdn& source) {
}
bool
Option6ClientFqdn::getFlag(const Flag flag) const {
// Caller should query for one of the: N, S or O flags. However, enumerator
// value of 0x3 is valid (because it belongs to the range between the
// lowest and highest enumerator). The value 0x3 represents two flags:
// S and O and would cause ambiguity. Therefore, we selectively check
// that the flag is equal to one of the explicit enumerator values. If
// not, throw an exception.
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(InvalidFqdnOptionFlags, "invalid DHCPv6 Client FQDN"
isc_throw(InvalidOption6FqdnFlags, "invalid DHCPv6 Client FQDN"
<< " Option flag specified, expected N, S or O");
}
......@@ -296,14 +308,16 @@ Option6ClientFqdn::getFlag(const Flag flag) const {
}
void
Option6ClientFqdn::setFlag(const Flag flag, const bool set_flag) {
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.
// 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(InvalidFqdnOptionFlags, "invalid DHCPv6 Client FQDN"
isc_throw(InvalidOption6FqdnFlags, "invalid DHCPv6 Client FQDN"
<< " Option flag " << std::hex
<< static_cast<int>(flag) << std::dec
<< "is being set. Expected combination of N, S and O");
<< "is being set. Expected: N, S or O");
}
// Copy the current flags into local variable. That way we will be able
......@@ -387,15 +401,14 @@ std::string
Option6ClientFqdn::toText(int indent) {
std::ostringstream stream;
std::string in(indent, ' '); // base indentation
std::string in_add(2, ' '); // second-level indentation is 2 spaces long
stream << in << "type=" << type_ << "(CLIENT_FQDN)" << std::endl
<< in << "flags:" << std::endl
<< in << in_add << "N=" << (getFlag(FLAG_N) ? "1" : "0") << std::endl
<< in << in_add << "O=" << (getFlag(FLAG_O) ? "1" : "0") << std::endl
<< in << in_add << "S=" << (getFlag(FLAG_S) ? "1" : "0") << std::endl
<< in << "domain-name='" << getDomainName() << "' ("
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")
<< ")" << std::endl;
<< ")";
return (stream.str());
}
......
......@@ -25,16 +25,16 @@ namespace dhcp {
/// @brief Exception thrown when invalid flags have been specified for
/// DHCPv6 Client Fqdn %Option.
class InvalidFqdnOptionFlags : public Exception {
class InvalidOption6FqdnFlags : public Exception {
public:
InvalidFqdnOptionFlags(const char* file, size_t line, const char* what) :
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 InvalidFqdnOptionDomainName : public Exception {
class InvalidOption6FqdnDomainName : public Exception {
public:
InvalidFqdnOptionDomainName(const char* file, size_t line,
InvalidOption6FqdnDomainName(const char* file, size_t line,
const char* what) :
isc::Exception(file, line, what) {}
};
......@@ -91,19 +91,13 @@ class Option6ClientFqdnImpl;
class Option6ClientFqdn : public Option {
public:
/// @brief Enumeration holding different flags used in the Client
/// FQDN %Option.
enum Flag {
FLAG_S = 0x01,
FLAG_O = 0x02,
FLAG_N = 0x04
};
/// @brief Type of the domain-name: partial or full.
enum DomainNameType {
PARTIAL,
FULL
};
///
///@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;
......@@ -111,16 +105,22 @@ public:
/// @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 flag a combination of flags to be stored in flags field.
/// @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 flag,
explicit Option6ClientFqdn(const uint8_t flags,
const std::string& domain_name,
const DomainNameType domain_name_type = FULL);
......@@ -129,8 +129,8 @@ public:
/// This constructor creates an instance of the option with empty
/// domain-name. This domain-name is marked partial.
///
/// @param flag a combination of flags to be stored in flags field.
Option6ClientFqdn(const uint8_t flag);
/// @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.
///
......@@ -157,18 +157,30 @@ public:
/// @brief Checks if the specified flag of the DHCPv6 Client FQDN %Option
/// is set.
///
/// @param flag an enum value specifying the flag to be checked.
/// 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 Flag flag) const;
bool getFlag(const uint8_t flag) const;
/// @brief Modifies the value of the specified DHCPv6 Client Fqdn %Option
/// flag.
///
/// @param flag an enum value specifying the flag to be modified.
/// 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 Flag flag, const bool set);
void setFlag(const uint8_t flag, const bool set);
/// @brief Sets the flag field value to 0.
void resetFlags();
......
......@@ -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;
......
......@@ -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.
///
......
......@@ -24,15 +24,6 @@ namespace {
using namespace isc;
using namespace isc::dhcp;
// Redefine option flags here as uint8_t. They will be used to initialize
// elements of the arrays that are used in tests below. Note that use of
// enum values defined in Option6ClientFqdn class may cause compilation issues
// during uint8_t arrays initialization. That is because the underlying
// integral type used to represent enums is larger than one byte.
const uint8_t FLAG_S = 0x01;
const uint8_t FLAG_O = 0x02;
const uint8_t FLAG_N = 0x04;
// This test verifies that constructor accepts empty partial domain-name but
// does not accept empty fully qualified domain name.
TEST(Option6ClientFqdnTest, constructEmptyName) {
......@@ -52,12 +43,12 @@ TEST(Option6ClientFqdnTest, constructEmptyName) {
// Constructor should not accept empty fully qualified domain name.
EXPECT_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "",
Option6ClientFqdn::FULL),
InvalidFqdnOptionDomainName);
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),
InvalidFqdnOptionDomainName);
InvalidOption6FqdnDomainName);
// Try different constructor.
ASSERT_NO_THROW(
......@@ -129,10 +120,40 @@ TEST(Option6ClientFqdnTest, copyConstruct) {
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));