Commit 1ceca44c authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[3082] Implemented DHCPv4 Client FQDN Option.

parent 72bcad54
......@@ -23,6 +23,7 @@ libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
libb10_dhcp___la_SOURCES += iface_mgr_sun.cc
libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
libb10_dhcp___la_SOURCES += option4_client_fqdn.cc option4_client_fqdn.h
libb10_dhcp___la_SOURCES += option6_ia.cc option6_ia.h
libb10_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
......
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <dhcp/dhcp4.h>
#include <dhcp/option4_client_fqdn.h>
#include <dns/labelsequence.h>
#include <util/buffer.h>
#include <util/io_utilities.h>
#include <util/strutil.h>
#include <sstream>
namespace isc {
namespace dhcp {
class Option4ClientFqdnImpl {
public:
uint8_t flags_;
Option4ClientFqdn::Rcode rcode1_;
Option4ClientFqdn::Rcode rcode2_;
boost::shared_ptr<isc::dns::Name> domain_name_;
Option4ClientFqdn::DomainNameType domain_name_type_;
Option4ClientFqdnImpl(const uint8_t flag,
const Option4ClientFqdn::Rcode& rcode,
const std::string& domain_name,
const Option4ClientFqdn::DomainNameType name_type);
Option4ClientFqdnImpl(OptionBufferConstIter first,
OptionBufferConstIter last);
Option4ClientFqdnImpl(const Option4ClientFqdnImpl& source);
Option4ClientFqdnImpl& operator=(const Option4ClientFqdnImpl& source);
void setDomainName(const std::string& domain_name,
const Option4ClientFqdn::DomainNameType name_type);
static void checkFlags(const uint8_t flags);
void parseWireData(OptionBufferConstIter first,
OptionBufferConstIter last);
};
Option4ClientFqdnImpl::
Option4ClientFqdnImpl(const uint8_t flag,
const Option4ClientFqdn::Rcode& rcode,
const std::string& domain_name,
const Option4ClientFqdn::DomainNameType name_type)
: flags_(flag),
rcode1_(rcode),
rcode2_(rcode),
domain_name_(),
domain_name_type_(name_type) {
// Check if flags are correct.
checkFlags(flags_);
// Set domain name. It may throw an exception if domain name has wrong
// format.
setDomainName(domain_name, name_type);
}
Option4ClientFqdnImpl::Option4ClientFqdnImpl(OptionBufferConstIter first,
OptionBufferConstIter last)
: rcode1_(Option4ClientFqdn::RCODE_CLIENT()),
rcode2_(Option4ClientFqdn::RCODE_CLIENT()) {
parseWireData(first, last);
// Verify that flags value was correct.
checkFlags(flags_);
}
Option4ClientFqdnImpl::
Option4ClientFqdnImpl(const Option4ClientFqdnImpl& source)
: flags_(source.flags_),
rcode1_(source.rcode1_),
rcode2_(source.rcode2_),
domain_name_(),
domain_name_type_(source.domain_name_type_) {
if (source.domain_name_) {
domain_name_.reset(new isc::dns::Name(*source.domain_name_));
}
}
Option4ClientFqdnImpl&
Option4ClientFqdnImpl::operator=(const Option4ClientFqdnImpl& source) {
domain_name_.reset(new isc::dns::Name(*source.domain_name_));
// Assignment is exception safe.
flags_ = source.flags_;
rcode1_ = source.rcode1_;
rcode2_ = source.rcode2_;
domain_name_type_ = source.domain_name_type_;
return (*this);
}
void
Option4ClientFqdnImpl::
setDomainName(const std::string& domain_name,
const Option4ClientFqdn::DomainNameType name_type) {
// domain-name must be trimmed. Otherwise, string comprising spaces only
// would be treated as a fully qualified name.
std::string name = isc::util::str::trim(domain_name);
if (name.empty()) {
if (name_type == Option4ClientFqdn::FULL) {
isc_throw(InvalidOption4ClientFqdnDomainName,
"fully qualified domain-name must not be empty"
<< " when setting new domain-name for DHCPv4 Client"
<< " FQDN Option");
}
// The special case when domain-name is empty is marked by setting the
// pointer to the domain-name object to NULL.
domain_name_.reset();
} else {
try {
domain_name_.reset(new isc::dns::Name(name));
domain_name_type_ = name_type;
} catch (const Exception& ex) {
isc_throw(InvalidOption4ClientFqdnDomainName,
"invalid domain-name value '"
<< domain_name << "' when setting new domain-name for"
<< " DHCPv4 Client FQDN Option");
}
}
}
void
Option4ClientFqdnImpl::checkFlags(const uint8_t flags) {
// The Must Be Zero (MBZ) bits must not be set.
if ((flags & ~Option4ClientFqdn::FLAG_MASK) != 0) {
isc_throw(InvalidOption4ClientFqdnFlags,
"invalid DHCPv4 Client FQDN Option flags: 0x"
<< std::hex << static_cast<int>(flags) << std::dec);
}
// According to RFC 4702, section 2.1. if the N bit is 1, the S bit
// MUST be 0. Checking it here.
if ((flags & (Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S))
== (Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S)) {
isc_throw(InvalidOption4ClientFqdnFlags,
"both N and S flag of the DHCPv4 Client FQDN Option are set."
<< " According to RFC 4702, if the N bit is 1 the S bit"
<< " MUST be 0");
}
}
void
Option4ClientFqdnImpl::parseWireData(OptionBufferConstIter first,
OptionBufferConstIter last) {
// Buffer must comprise at least one byte with the flags.
// The domain-name may be empty.
if (std::distance(first, last) < Option4ClientFqdn::FIXED_FIELDS_LEN) {
isc_throw(OutOfRange, "DHCPv4 Client FQDN Option ("
<< DHO_FQDN << ") is truncated");
}
// Parse flags
flags_ = *(first++);
// Parse RCODE1 and RCODE2.
rcode1_ = Option4ClientFqdn::Rcode(*(first++));
rcode2_ = Option4ClientFqdn::Rcode(*(first++));
// Parse domain-name if any.
if (std::distance(first, last) > 0) {
// The FQDN may comprise a partial domain-name. In this case it lacks
// terminating 0. If this is the case, we will need to add zero at
// the end because Name object constructor requires it.
if (*(last - 1) != 0) {
// Create temporary buffer and add terminating zero.
OptionBuffer buf(first, last);
buf.push_back(0);
// Reset domain name.
isc::util::InputBuffer name_buf(&buf[0], buf.size());
domain_name_.reset(new isc::dns::Name(name_buf));
// Terminating zero was missing, so set the domain-name type
// to partial.
domain_name_type_ = Option4ClientFqdn::PARTIAL;
} else {
// We are dealing with fully qualified domain name so there is
// no need to add terminating zero. Simply pass the buffer to
// Name object constructor.
isc::util::InputBuffer name_buf(&(*first),
std::distance(first, last));
domain_name_.reset(new isc::dns::Name(name_buf));
// Set the domain-type to fully qualified domain name.
domain_name_type_ = Option4ClientFqdn::FULL;
}
}
}
Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag, const Rcode& rcode)
: Option(Option::V4, DHO_FQDN),
impl_(new Option4ClientFqdnImpl(flag, rcode, "", PARTIAL)) {
}
Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag,
const Rcode& rcode,
const std::string& domain_name,
const DomainNameType domain_name_type)
: Option(Option::V4, DHO_FQDN),
impl_(new Option4ClientFqdnImpl(flag, rcode, domain_name,
domain_name_type)) {
}
Option4ClientFqdn::Option4ClientFqdn(OptionBufferConstIter first,
OptionBufferConstIter last)
: Option(Option::V4, DHO_FQDN, first, last),
impl_(new Option4ClientFqdnImpl(first, last)) {
}
Option4ClientFqdn::~Option4ClientFqdn() {
delete(impl_);
}
Option4ClientFqdn::Option4ClientFqdn(const Option4ClientFqdn& source)
: Option(source),
impl_(new Option4ClientFqdnImpl(*source.impl_)) {
}
Option4ClientFqdn&
Option4ClientFqdn::operator=(const Option4ClientFqdn& source) {
Option4ClientFqdnImpl* old_impl = impl_;
impl_ = new Option4ClientFqdnImpl(*source.impl_);
delete(old_impl);
return (*this);
}
bool
Option4ClientFqdn::getFlag(const Flag flag) const {
// Caller should query for one of the: E, N, S or O flags. However, there
// are valid enumerator values which should not be accepted by this function.
// For example a value of 0x3 is valid (because it belongs to the range between the
// lowest and highest enumerator). The value 0x3 represents two flags:
// S and O and would cause ambiguity. Therefore, we selectively check
// that the flag is equal to one of the explicit enumerator values. If
// not, throw an exception.
if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N && flag != FLAG_E) {
isc_throw(InvalidOption4ClientFqdnFlags, "invalid DHCPv4 Client FQDN"
<< " Option flag specified, expected E, N, S or O");
}
return ((impl_->flags_ & flag) != 0);
}
void
Option4ClientFqdn::setFlag(const Flag flag, const bool set_flag) {
// Check that flag is in range between 0x1 and 0x7. Note that this
// allows to set or clear multiple flags concurrently.
if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) {
isc_throw(InvalidOption4ClientFqdnFlags, "invalid DHCPv4 Client FQDN"
<< " Option flag " << std::hex
<< static_cast<int>(flag) << std::dec
<< "is being set. Expected combination of E, N, S and O");
}
// Copy the current flags into local variable. That way we will be able
// to test new flags settings before applying them.
uint8_t new_flag = impl_->flags_;
if (set_flag) {
new_flag |= flag;
} else {
new_flag &= ~flag;
}
// Check new flags. If they are valid, apply them.
Option4ClientFqdnImpl::checkFlags(new_flag);
impl_->flags_ = new_flag;
}
void
Option4ClientFqdn::setRcode(const Rcode& rcode) {
impl_->rcode1_ = rcode;
impl_->rcode2_ = rcode;
}
void
Option4ClientFqdn::resetFlags() {
impl_->flags_ = 0;
}
std::string
Option4ClientFqdn::getDomainName() const {
if (impl_->domain_name_) {
return (impl_->domain_name_->toText(impl_->domain_name_type_ ==
PARTIAL));
}
// If an object holding domain-name is NULL it means that the domain-name
// is empty.
return ("");
}
void
Option4ClientFqdn::packDomainName(isc::util::OutputBuffer& buf) const {
// Domain name, encoded as a set of labels.
isc::dns::LabelSequence labels(*impl_->domain_name_);
if (labels.getDataLength() > 0) {
size_t read_len = 0;
const uint8_t* data = labels.getData(&read_len);
if (impl_->domain_name_type_ == PARTIAL) {
--read_len;
}
buf.writeData(data, read_len);
}
}
void
Option4ClientFqdn::setDomainName(const std::string& domain_name,
const DomainNameType domain_name_type) {
impl_->setDomainName(domain_name, domain_name_type);
}
void
Option4ClientFqdn::resetDomainName() {
setDomainName("", PARTIAL);
}
Option4ClientFqdn::DomainNameType
Option4ClientFqdn::getDomainNameType() const {
return (impl_->domain_name_type_);
}
void
Option4ClientFqdn::pack(isc::util::OutputBuffer& buf) {
// Header = option code and length.
packHeader(buf);
// Flags field.
buf.writeUint8(impl_->flags_);
// RCODE1 and RCODE2
buf.writeUint8(impl_->rcode1_.getCode());
buf.writeUint8(impl_->rcode2_.getCode());
// Domain name.
packDomainName(buf);
}
void
Option4ClientFqdn::unpack(OptionBufferConstIter first,
OptionBufferConstIter last) {
setData(first, last);
impl_->parseWireData(first, last);
}
std::string
Option4ClientFqdn::toText(int indent) {
std::ostringstream stream;
std::string in(indent, ' '); // base indentation
std::string in_add(2, ' '); // second-level indentation is 2 spaces long
stream << in << "type=" << type_ << "(CLIENT_FQDN)" << std::endl
<< in << "flags:" << std::endl
<< in << in_add << "N=" << (getFlag(FLAG_N) ? "1" : "0") << std::endl
<< in << in_add << "E=" << (getFlag(FLAG_E) ? "1" : "0") << std::endl
<< in << in_add << "O=" << (getFlag(FLAG_O) ? "1" : "0") << std::endl
<< in << in_add << "S=" << (getFlag(FLAG_S) ? "1" : "0") << std::endl
<< in << "domain-name='" << getDomainName() << "' ("
<< (getDomainNameType() == PARTIAL ? "partial" : "full")
<< ")" << std::endl;
return (stream.str());
}
uint16_t
Option4ClientFqdn::len() {
// If domain name is partial, the NULL terminating character
// is not included and the option length have to be adjusted.
uint16_t domain_name_length = impl_->domain_name_type_ == FULL ?
impl_->domain_name_->getLength() : impl_->domain_name_->getLength() - 1;
return (getHeaderLen() + FIXED_FIELDS_LEN + domain_name_length);
}
} // end of isc::dhcp namespace
} // end of isc namespace
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#ifndef OPTION4_CLIENT_FQDN_H
#define OPTION4_CLIENT_FQDN_H
#include <dhcp/option.h>
#include <dns/name.h>
#include <string>
namespace isc {
namespace dhcp {
/// @brief Exception thrown when invalid flags have been specified for
/// DHCPv4 Client FQDN %Option.
class InvalidOption4ClientFqdnFlags : public Exception {
public:
InvalidOption4ClientFqdnFlags(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) {}
};
/// @brief Exception thrown when invalid domain name is specified.
class InvalidOption4ClientFqdnDomainName : public Exception {
public:
InvalidOption4ClientFqdnDomainName(const char* file, size_t line,
const char* what) :
isc::Exception(file, line, what) {}
};
/// Forward declaration to implementation of @c Option4ClientFqdn class.
class Option4ClientFqdnImpl;
/// @brief Represents DHCPv4 Client FQDN %Option (code 81).
///
/// This option has been defined in the RFC 4702 and it has a following
/// structure:
/// - Code (1 octet) - option code (always equal to 81).
/// - Len (1 octet) - a length of the option.
/// - Flags (1 octet) - a field carrying "NEOS" flags described below.
/// - RCODE1 (1 octet) - deprecated field which should be set to 0 by the client
/// and set to 255 by the server.
/// - RCODE2 (1 octet) - deprecated, should be used in the same way as RCODE1.
/// - Domain Name - variable length field comprising partial or fully qualified
/// domain name.
///
/// The flags field has the following structure:
/// @code
/// 0 1 2 3 4 5 6 7
/// +-+-+-+-+-+-+-+-+
/// | MBZ |N|E|O|S|
/// +-+-+-+-+-+-+-+-+
/// @endcode
/// where:
/// - N flag specifies whether server should (0) or should not (1) perform DNS
/// Update,
/// - E flag indicates encoding of the Domain Name field. If this flag is set to 1
/// it indicates canonical wire format without compression. 0 indicates the deprecated
/// ASCII format.
/// - O flag is set by the server to indicate that it has overridden client's
/// preference set with the S bit.
/// - S flag specifies whether server should (1) or should not (0) perform
/// forward (FQDN-to-address) updates.
///
/// This class exposes a set of functions to modify flags and check their
/// correctness.
///
/// Domain names being carried by DHCPv4 Client Fqdn %Option can be fully
/// qualified or partial. Partial domain names are encoded similar to the
/// fully qualified domain names, except that they lack terminating zero
/// at the end of their wire representation. It is also accepted to create an
/// instance of this option which has empty domain-name. Clients use empty
/// domain-names to indicate that server should generate complete fully
/// qualified domain-name.
///
/// RFC 4702 mandates that the DHCP client sets RCODE1 and RCODE2 to 0 and that
/// server sets them to 255. This class allows to set the value for these
/// fields and both fields are always set to the same value. There is no way
/// to set them separately (e.g. set different value for RCODE1 and RCODE2).
/// However, there are no use cases which would require it.
///
/// <b>Design choice:</b> This class uses pimpl idiom to separate the interface
/// from implementation specifics. Implementations may use different approaches
/// to handle domain names (mostly validation of the domain-names). The existing
/// @c isc::dns::Name class is a natural (and the simplest) choice to handle
/// domain-names. Use of this class however, implies that libdhcp must be linked
/// with libdns. At some point these libraries may need to be separated, i.e. to
/// support compilation and use of standalone DHCP server. This will require
/// that the part of implementation which deals with domain-names is modified to
/// not use classes from libdns. These changes will be transparent for this
/// interface.
class Option4ClientFqdn : public Option {
public:
/// @brief Enumeration holding different flags used in the Client
/// FQDN %Option.
enum Flag {
FLAG_S = 0x01,
FLAG_O = 0x02,
FLAG_E = 0x04,
FLAG_N = 0x08
};
/// @brief Represents the value of one of the RCODE1 or RCODE2 fields.
///
/// Typically, RCODE values are set to 255 by the server and to 0 by the
/// clients (as per RFC 4702).
class Rcode {
public:
Rcode(const uint8_t rcode)
: rcode_(rcode) { }
/// @brief Returns the value of the RCODE.
///
/// Returned value can be directly used to create the on-wire format
/// of the DHCPv4 Client FQDN %Option.
uint8_t getCode() const {
return (rcode_);
}
private:
uint8_t rcode_;
};
/// @brief Type of the domain-name: partial or full.
enum DomainNameType {
PARTIAL,
FULL
};
/// @brief Mask which zeroes MBZ flag bits.
static const uint8_t FLAG_MASK = 0xF;
/// @brief The size of the fixed fields within DHCPv4 Client Fqdn %Option.
///
/// The fixed fields are:
/// - Flags
/// - RCODE1
/// - RCODE2
static const uint16_t FIXED_FIELDS_LEN = 3;
/// @brief Constructor, creates option instance using flags and domain name.
///
/// This constructor is used to create instance of the option which will be
/// included in outgoing messages.
///
/// @param flag a combination of flags to be stored in flags field.
/// @param rcode @c Rcode object representing a value for RCODE1 and RCODE2
/// fields of the option. Both fields are assigned the same value encapsulated
/// by the parameter.
/// @param domain_name a name to be stored in the domain-name field.
/// @param partial_domain_name indicates if the domain name is partial
/// (if true) or full (false).
explicit Option4ClientFqdn(const uint8_t flag,
const Rcode& rcode,
const std::string& domain_name,
const DomainNameType domain_name_type = FULL);
/// @brief Constructor, creates option instance with empty domain name.
///
/// This constructor creates an instance of the option with empty
/// domain-name. This domain-name is marked partial.
///
/// @param flag a combination of flags to be stored in flags field.
/// @param rcode @c Rcode object representing a value for RCODE1 and RCODE2
/// fields. Both fields are assigned the same value encapsulated by this
/// parameter.
Option4ClientFqdn(const uint8_t flag, const Rcode& rcode);
/// @brief Constructor, creates an option instance from part of the buffer.
///
/// This constructor is mainly used to parse options in the received
/// messages. Function parameters specify buffer bounds from which the
/// option should be created. The size of the buffer chunk, specified by
/// the constructor's parameters should be equal or larger than the size
/// of the option. Otherwise, constructor will throw an exception.
///
/// @param first the lower bound of the buffer to create option from.
/// @param last the upper bound of the buffer to create option from.
explicit Option4ClientFqdn(OptionBufferConstIter first,
OptionBufferConstIter last);
/// @brief Copy constructor
Option4ClientFqdn(const Option4ClientFqdn& source);
/// @brief Destructor
virtual ~Option4ClientFqdn();
/// @brief Assignment operator
Option4ClientFqdn& operator=(const Option4ClientFqdn& source);
/// @brief Checks if the specified flag of the DHCPv4 Client FQDN %Option
/// is set.
///
/// @param flag an enum value specifying the flag to be checked.
///
/// @return true if the bit of the specified flag is set, false otherwise.
bool getFlag(const Flag flag) const;
/// @brief Modifies the value of the specified DHCPv4 Client Fqdn %Option
/// flag.
///
/// @param flag an enum value specifying the flag to be modified.
/// @param set a boolean value which indicates whether flag should be
/// set (true), or cleared (false).
void setFlag(const Flag flag, const bool set);
/// @brief Sets the flag field value to 0.