Commit 95701a2c authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

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

parent bb7465ef
......@@ -26,6 +26,7 @@ libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
libb10_dhcp___la_SOURCES += option6_ia.cc option6_ia.h
libb10_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
libb10_dhcp___la_SOURCES += option6_client_fqdn.cc option6_client_fqdn.h
libb10_dhcp___la_SOURCES += option_int.h
libb10_dhcp___la_SOURCES += option_int_array.h
libb10_dhcp___la_SOURCES += option.cc option.h
......
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <dhcp/dhcp6.h>
#include <dhcp/option6_client_fqdn.h>
#include <dns/labelsequence.h>
namespace isc {
namespace dhcp {
Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag,
const std::string& domain_name,
const DomainNameType domain_name_type)
: Option(Option::V6, D6O_CLIENT_FQDN),
flags_(flag),
domain_name_(NULL),
domain_name_type_(domain_name_type) {
// Check if flags are correct.
checkFlags(flags_);
try {
domain_name_ = new isc::dns::Name(domain_name);
} catch (const Exception& ex) {
isc_throw(InvalidFqdnOptionDomainName, "invalid domain-name value: "
<< domain_name);
}
}
Option6ClientFqdn::Option6ClientFqdn(OptionBufferConstIter first,
OptionBufferConstIter last)
: Option(Option::V6, D6O_CLIENT_FQDN, first, last),
domain_name_(NULL) {
}
Option6ClientFqdn::~Option6ClientFqdn() {
delete (domain_name_);
}
bool
Option6ClientFqdn::getFlag(const Flag flag) const {
// Caller should query for one of the: N, S or O flags. However, enumerator
// value of 0x3 is valid (because it belongs to the range between the
// lowest and highest enumerator). The value 0x3 represents two flags:
// S and O and would cause ambiguity. Therefore, we selectively check
// that the flag is equal to one of the explicit enumerator values. If
// not, throw an exception.
if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N) {
isc_throw(InvalidFqdnOptionFlags, "invalid DHCPv6 Client FQDN"
<< " Option flag specified, expected N, S or O");
}
return ((flags_ & flag) != 0);
}
void
Option6ClientFqdn::setFlag(const Flag flag, const bool set_flag) {
// Check that flag is in range between 0x1 and 0x7. Note that this
// allows to set or clear multiple flags concurrently.
if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) {
isc_throw(InvalidFqdnOptionFlags, "invalid DHCPv6 Client FQDN"
<< " Option flag " << std::hex
<< static_cast<int>(flag) << std::dec
<< "is being set. Expected combination of N, S and O");
}
// Copy the current flags into local variable. That way we will be able
// to test new flags settings before applying them.
uint8_t new_flag = flags_;
if (set_flag) {
new_flag |= flag;
} else {
new_flag &= ~flag;
}
// Check new flags. If they are valid, apply them.
checkFlags(new_flag);
flags_ = new_flag;
}
void
Option6ClientFqdn::pack(isc::util::OutputBuffer& buf) {
// Header = option code and length.
packHeader(buf);
// Flags field.
buf.writeUint8(flags_);
// Domain name, encoded as a set of labels.
isc::dns::LabelSequence labels(*domain_name_);
if (labels.getDataLength() > 0) {
size_t read_len = 0;
const uint8_t* data = labels.getData(&read_len);
if (domain_name_type_ == PARTIAL) {
--read_len;
}
buf.writeData(data, read_len);
}
}
void
Option6ClientFqdn::unpack(OptionBufferConstIter,
OptionBufferConstIter) {
}
std::string
Option6ClientFqdn::toText(int) {
return std::string();
}
uint16_t
Option6ClientFqdn::len() {
// If domain name is partial, the NULL terminating character
// is not included and the option length have to be adjusted.
uint16_t domain_name_length = domain_name_type_ == FULL ?
domain_name_->getLength() : domain_name_->getLength() - 1;
return (getHeaderLen() + FLAG_FIELD_LEN + domain_name_length);
}
void
Option6ClientFqdn::checkFlags(const uint8_t flags) {
// The Must Be Zero (MBZ) bits must not be set.
if ((flags & ~FLAG_MASK) != 0) {
isc_throw(InvalidFqdnOptionFlags,
"invalid DHCPv6 Client FQDN Option flags: 0x"
<< std::hex << static_cast<int>(flags) << std::dec);
}
// According to RFC 4704, section 4.1. if the N bit is 1, the S bit
// MUST be 0. Checking it here.
if ((flags & (FLAG_N | FLAG_S)) == (FLAG_N | FLAG_S)) {
isc_throw(InvalidFqdnOptionFlags,
"both N and S flag of the DHCPv6 Client FQDN Option are set."
<< " According to RFC 4704, if the N bit is 1 the S bit"
<< " MUST be 0");
}
}
} // end of isc::dhcp namespace
} // end of isc namespace
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#ifndef OPTION6_CLIENT_FQDN_H
#define OPTION6_CLIENT_FQDN_H
#include <dhcp/option.h>
#include <dns/name.h>
#include <string>
namespace isc {
namespace dhcp {
/// @brief Exception thrown when invalid flags have been specified for
/// DHCPv6 Client Fqdn %Option.
class InvalidFqdnOptionFlags : public Exception {
public:
InvalidFqdnOptionFlags(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) {}
};
/// @brief Exception thrown when invalid domain name is specified.
class InvalidFqdnOptionDomainName : public Exception {
public:
InvalidFqdnOptionDomainName(const char* file, size_t line,
const char* what) :
isc::Exception(file, line, what) {}
};
/// @brief Represents DHCPv6 Client FQDN %Option (code 39).
///
/// This option has been defined in the RFC 4704 and it has a following
/// structure:
/// - option-code = 39 (2 octets)
/// - option-len (2 octets)
/// - flags (1 octet)
/// - domain-name - variable length field comprising partial or fully qualified
/// domain name.
///
/// The flags field has the following structure:
/// @code
/// 0 1 2 3 4 5 6 7
/// +-+-+-+-+-+-+-+-+
/// | MBZ |N|O|S|
/// +-+-+-+-+-+-+-+-+
/// @endcode
/// where:
/// - N flag specifies whether server should (0) or should not (1) perform DNS
/// Update,
/// - O flag is set by the server to indicate that it has overriden client's
/// preferrence set with the S bit.
/// - S flag specifies whether server should (1) or should not (0) perform
/// forward (FQDN-to-address) updates.
///
/// This class exposes a set of functions to modify flags and check their
/// correctness.
///
/// Domain names being carried by DHCPv6 Client Fqdn %Option can be fully
/// qualified or partial. Partial domain names are encoded similar to the
/// fully qualified domain names, except that they lack terminating zero
/// at the end of their wire representation.
class Option6ClientFqdn : public Option {
public:
/// @brief Enumeration holding different flags used in the Client
/// FQDN %Option.
enum Flag {
FLAG_S = 0x01,
FLAG_O = 0x02,
FLAG_N = 0x04
};
/// @brief Type of the domain-name: partial or full.
enum DomainNameType {
PARTIAL,
FULL
};
/// @brief Mask which zeroes MBZ flag bits.
static const uint8_t FLAG_MASK = 0x7;
/// @brief The length of the flag field within DHCPv6 Client Fqdn %Option.
static const uint16_t FLAG_FIELD_LEN = 1;
/// @brief Constructor, creates option instance using flags and domain name.
///
/// This constructor is used to create instance of the option which will be
/// included in outgoing messages.
///
/// @param flag a combination of flags to be stored in flags field.
/// @param domain_name a name to be stored in the domain-name field.
/// @param partial_domain_name indicates if the domain name is partial
/// (if true) or full (false).
explicit Option6ClientFqdn(const uint8_t flag,
const std::string& domain_name,
const DomainNameType domain_name_type = FULL);
/// @brief Constructor, creates an option instance from part of the buffer.
///
/// This constructor is mainly used to parse options in the received
/// messages. Function parameters specify buffer bounds from which the
/// option should be created. The size of the buffer chunk, specified by
/// the constructor's paramaters should be equal or larger than the size
/// of the option. Otherwise, constructor will throw an exception.
///
/// @param first the lower bound of the buffer to create option from.
/// @param last the upper bound of the buffer to create option from.
explicit Option6ClientFqdn(OptionBufferConstIter first,
OptionBufferConstIter last);
/// @brief Destructor
virtual ~Option6ClientFqdn();
/// @brief Checks if the specified flag of the DHCPv6 Client FQDN %Option
/// is set.
///
/// @param flag an enum value specifying the flag to be checked.
///
/// @return true if the bit of the specified flag is set, false otherwise.
bool getFlag(const Flag flag) const;
/// @brief Modifies the value of the specified DHCPv6 Client Fqdn %Option
/// flag.
///
/// @param flag an enum value specifying the flag to be modified.
/// @param set a boolean value which indicates whether flag should be
/// set (true), or cleared (false).
void setFlag(const Flag flag, const bool set);
/// @brief Writes option in the wire format into a buffer.
///
/// @param [out] buf output buffer where option data will be stored.
virtual void pack(isc::util::OutputBuffer& buf);
/// @brief Parses option from the received buffer.
///
/// Method creates an instance of the DHCPv6 Client FQDN %Option from the
/// wire format. Parameters specify the bounds of the buffer to read option
/// data from. The size of the buffer limited by the specified parameters
/// should be equal or larger than size of the option (including its
/// header). Otherwise exception will be thrown.
///
/// @param first lower bound of the buffer to parse option from.
/// @param last upper bound of the buffer to parse option from.
virtual void unpack(OptionBufferConstIter first,
OptionBufferConstIter last);
/// @brief Returns string representation of the option.
///
/// The string returned by the method comprises the bit value of each
/// option flag and the domain-name.
///
/// @param indent number of spaces before printed text.
///
/// @return string with text representation.
virtual std::string toText(int indent = 0);
/// @brief Returns length of the complete option (data length +
/// DHCPv6 option header).
///
/// @return length of the option.
virtual uint16_t len();
private:
/// @brief Verifies that flags are correct.
///
/// Function throws @c isc::dhcp::InvalidFqdnOptionFlags exception if
/// current setting of DHCPv6 Client Fqdn %Option flags is invalid.
/// In particular, it checks that if N is set, S is cleared.
///
/// @param flags DHCPv6 Client FQDN %Option flags to be checked.
///
/// @throw isc::dhcp::InvalidFqdnOptionFlags if flags are incorrect.
static void checkFlags(const uint8_t flags);
uint8_t flags_;
dns::Name* domain_name_;
DomainNameType domain_name_type_;
};
/// A pointer to the @c Option6ClientFqdn object.
typedef boost::shared_ptr<Option6ClientFqdn> Option6ClientFqdnPtr;
} // namespace isc::dhcp
} // namespace isc
#endif // OPTION6_CLIENT_FQDN_H
......@@ -32,6 +32,7 @@ libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
libdhcp___unittests_SOURCES += option6_addrlst_unittest.cc
libdhcp___unittests_SOURCES += option6_client_fqdn_unittest.cc
libdhcp___unittests_SOURCES += option6_ia_unittest.cc
libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
libdhcp___unittests_SOURCES += option_int_unittest.cc
......
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <dhcp/option6_client_fqdn.h>
#include <dns/name.h>
#include <util/buffer.h>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
namespace {
using namespace isc;
using namespace isc::dhcp;
class Option6ClientFqdnTest : public ::testing::Test {
public:
Option6ClientFqdnTest() { }
virtual ~Option6ClientFqdnTest() { }
};
TEST_F(Option6ClientFqdnTest, ctorInvalidFlags) {
// First, check that constructor does not throw an exception when
// valid flags values are provided. That way we eliminate the issue
// that constructor always throws exception.
uint8_t flags = 0;
ASSERT_NO_THROW(Option6ClientFqdn(flags, "myhost.example.com"));
// Invalid flags: The maximal value is 0x7 when all flag bits are set
// (00000111b). The flag value of 0x14 sets the bit from the Must Be
// Zero (MBZ) bitset (00001100b).
flags = 0x14;
EXPECT_THROW(Option6ClientFqdn(flags, "myhost.example.com"),
InvalidFqdnOptionFlags);
// According to RFC 4704, section 4.1. if the N bit is set the S bit MUST
// be zero. If both are set, constructor is expected to throw.
flags = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S;
EXPECT_THROW(Option6ClientFqdn(flags, "myhost.example.com"),
InvalidFqdnOptionFlags);
}
// This test verifies that if invalid domain name is used the constructor
// will throw appropriate exception.
TEST_F(Option6ClientFqdnTest, ctorInvalidName) {
// First, check that constructor does not throw when valid domain name
// is specified. That way we eliminate the possibility that constructor
// always throws exception.
ASSERT_NO_THROW(Option6ClientFqdn(0, "myhost.example.com"));
// Specify invalid domain name and expect that exception is thrown.
EXPECT_THROW(Option6ClientFqdn(0, "my...host.example.com"),
InvalidFqdnOptionDomainName);
}
// This test verifies that getFlag throws an exception if flag value of 0x3
// is specified.TThis test does not verify other invalid values, e.g. 0x5,
// 0x6 etc. because conversion of int values which do not belong to the range
// between the lowest and highest enumerator will give an undefined
// result.
TEST_F(Option6ClientFqdnTest, getFlag) {
boost::scoped_ptr<Option6ClientFqdn> option;
ASSERT_NO_THROW(
option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
);
ASSERT_TRUE(option);
// The 0x3 is a valid enumerator value (even though it is not explicitly
// included in the Option6ClientFqdn::Flag definition). The getFlag()
// function should not accept it. Only explicit values are accepted.
EXPECT_THROW(option->getFlag(static_cast<Option6ClientFqdn::Flag>(0x3)),
InvalidFqdnOptionFlags);
}
// This test verifies that flags can be modified and that incorrect flags
// are rejected.
TEST_F(Option6ClientFqdnTest, setFlag) {
// Create option instance. Check that constructor doesn't throw.
boost::scoped_ptr<Option6ClientFqdn> option;
ASSERT_NO_THROW(
option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
);
ASSERT_TRUE(option);
// All flags should be set to 0 initially.
ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
// Set N = 1
ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, true));
ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_N));
// Set O = 1
ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_O, true));
ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
// Set S = 1, this should throw exception because S and N must not
// be set in the same time.
ASSERT_THROW(option->setFlag(Option6ClientFqdn::FLAG_S, true),
InvalidFqdnOptionFlags);
// Set N = 0
ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, false));
ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
// Set S = 1, this should not result in exception because N has been
// cleared.
ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_S, true));
ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
// Set N = 1, this should result in exception because S = 1
ASSERT_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, true),
InvalidFqdnOptionFlags);
// Set O = 0
ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_O, false));
ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
// Try out of bounds settings.
uint8_t flags = 0;
ASSERT_THROW(option->setFlag(static_cast<Option6ClientFqdn::Flag>(flags),
true),
InvalidFqdnOptionFlags);
flags = 0x14;
ASSERT_THROW(option->setFlag(static_cast<Option6ClientFqdn::Flag>(flags),
true),
InvalidFqdnOptionFlags);
}
// This test verifies on-wire format of the option is correctly created.
TEST_F(Option6ClientFqdnTest, pack) {
// Create option instance. Check that constructor doesn't throw.
const uint8_t flags = Option6ClientFqdn::FLAG_S;
boost::scoped_ptr<Option6ClientFqdn> option;
ASSERT_NO_THROW(
option.reset(new Option6ClientFqdn(flags, "myhost.example.com"))
);
ASSERT_TRUE(option);
// Prepare on-wire format of the option.
isc::util::OutputBuffer buf(10);
ASSERT_NO_THROW(option->pack(buf));
// Prepare reference data.
const uint8_t ref_data[] = {
0, 39, 0, 21, // header
Option6ClientFqdn::FLAG_S, // flags
6, 109, 121, 104, 111, 115, 116, // myhost.
7, 101, 120, 97, 109, 112, 108, 101, // example.
3, 99, 111, 109, 0 // com.
};
size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
// Check if the buffer has the same length as the reference data,
// so as they can be compared directly.
ASSERT_EQ(ref_data_size, buf.getLength());
EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
}
// This test verifies on-wire format of the option with partial domain name
// is correctly created.
TEST_F(Option6ClientFqdnTest, packPartial) {
// Create option instance. Check that constructor doesn't throw.
const uint8_t flags = Option6ClientFqdn::FLAG_S;
boost::scoped_ptr<Option6ClientFqdn> option;
ASSERT_NO_THROW(
option.reset(new Option6ClientFqdn(flags, "myhost",
Option6ClientFqdn::PARTIAL))
);
ASSERT_TRUE(option);
// Prepare on-wire format of the option.
isc::util::OutputBuffer buf(10);
ASSERT_NO_THROW(option->pack(buf));
// Prepare reference data.
const uint8_t ref_data[] = {
0, 39, 0, 8, // header
Option6ClientFqdn::FLAG_S, // flags
6, 109, 121, 104, 111, 115, 116 // myhost
};
size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
// Check if the buffer has the same length as the reference data,
// so as they can be compared directly.
ASSERT_EQ(ref_data_size, buf.getLength());
EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
}
// This test verifies that the correct length of the option in on-wire
// format is returned.
TEST_F(Option6ClientFqdnTest, len) {
// Create option instance. Check that constructor doesn't throw.
boost::scoped_ptr<Option6ClientFqdn> option;
ASSERT_NO_THROW(
option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
);
ASSERT_TRUE(option);
// This option comprises a header (4 octets), flag field (1 octet),
// and wire representation of the domain name (length equal to the
// length of the string representation of the domain name + 1).
EXPECT_EQ(25, option->len());
// Let's check that the size will change when domain name of a different
// size is used.
ASSERT_NO_THROW(
option.reset(new Option6ClientFqdn(0, "example.com"))
);
ASSERT_TRUE(option);
EXPECT_EQ(18, option->len());
ASSERT_NO_THROW(
option.reset(new Option6ClientFqdn(0, "myhost",
Option6ClientFqdn::PARTIAL))
);
ASSERT_TRUE(option);
EXPECT_EQ(12, option->len());
}
} // anonymous namespace
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment