Commit 1eb11784 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[master] Merge branch 'trac2827' (relay support in dhcp/Pkt6)

Conflicts:
	ChangeLog
parents d538f9ed 29c3f7f4
599. [func] tomek
libdhcp++: Pkt6 class is now able to parse and build relayed DHCPv6
messages.
(Trac #2827, git 29c3f7f4e82d7e85f0f5fb692345fd55092796b4)
bind10-1.0.0beta1 released on April 4, 2013 bind10-1.0.0beta1 released on April 4, 2013
598. [func]* jinmei 598. [func]* jinmei
......
// 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 // Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above // purpose with or without fee is hereby granted, provided that the above
...@@ -128,7 +128,9 @@ LibDHCP::optionFactory(Option::Universe u, ...@@ -128,7 +128,9 @@ LibDHCP::optionFactory(Option::Universe u,
size_t LibDHCP::unpackOptions6(const OptionBuffer& buf, size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
isc::dhcp::Option::OptionCollection& options) { isc::dhcp::Option::OptionCollection& options,
size_t* relay_msg_offset /* = 0 */,
size_t* relay_msg_len /* = 0 */) {
size_t offset = 0; size_t offset = 0;
size_t length = buf.size(); size_t length = buf.size();
...@@ -143,6 +145,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf, ...@@ -143,6 +145,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
while (offset + 4 <= length) { while (offset + 4 <= length) {
uint16_t opt_type = isc::util::readUint16(&buf[offset]); uint16_t opt_type = isc::util::readUint16(&buf[offset]);
offset += 2; offset += 2;
uint16_t opt_len = isc::util::readUint16(&buf[offset]); uint16_t opt_len = isc::util::readUint16(&buf[offset]);
offset += 2; offset += 2;
...@@ -151,6 +154,16 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf, ...@@ -151,6 +154,16 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
return (offset); return (offset);
} }
if (opt_type == D6O_RELAY_MSG && relay_msg_offset && relay_msg_len) {
// remember offset of the beginning of the relay-msg option
*relay_msg_offset = offset;
*relay_msg_len = opt_len;
// do not create that relay-msg option
offset += opt_len;
continue;
}
// Get all definitions with the particular option code. Note that option // Get all definitions with the particular option code. Note that option
// code is non-unique within this container however at this point we // code is non-unique within this container however at this point we
// expect to get one option definition with the particular code. If more // expect to get one option definition with the particular code. If more
...@@ -193,7 +206,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf, ...@@ -193,7 +206,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
} }
size_t LibDHCP::unpackOptions4(const OptionBuffer& buf, size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
isc::dhcp::Option::OptionCollection& options) { isc::dhcp::Option::OptionCollection& options) {
size_t offset = 0; size_t offset = 0;
// Get the list of stdandard option definitions. // Get the list of stdandard option definitions.
......
// 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 // Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above // purpose with or without fee is hereby granted, provided that the above
...@@ -115,14 +115,27 @@ public: ...@@ -115,14 +115,27 @@ public:
/// @brief Parses provided buffer as DHCPv6 options and creates Option objects. /// @brief Parses provided buffer as DHCPv6 options and creates Option objects.
/// ///
/// Parses provided buffer and stores created Option objects /// Parses provided buffer and stores created Option objects in options
/// in options container. /// container. The last two parameters are optional and are used in
/// relay parsing. If they are specified, relay-msg option is not created,
/// but rather those two parameters are specified to point out where
/// the relay-msg option resides and what is its length. This is perfromance
/// optimization that avoids unnecessary copying of potentially large
/// relay-msg option. It is not used for anything, except in the next
/// iteration its content will be treated as buffer to be parsed.
/// ///
/// @param buf Buffer to be parsed. /// @param buf Buffer to be parsed.
/// @param options Reference to option container. Options will be /// @param options Reference to option container. Options will be
/// put here. /// put here.
/// @param relay_msg_offset reference to a size_t structure. If specified,
/// offset to beginning of relay_msg option will be stored in it.
/// @param relay_msg_len reference to a size_t structure. If specified,
/// length of the relay_msg option will be stored in it.
/// @return offset to the first byte after last parsed option
static size_t unpackOptions6(const OptionBuffer& buf, static size_t unpackOptions6(const OptionBuffer& buf,
isc::dhcp::Option::OptionCollection& options); isc::dhcp::Option::OptionCollection& options,
size_t* relay_msg_offset = 0,
size_t* relay_msg_len = 0);
/// Registers factory method that produces options of specific option types. /// Registers factory method that produces options of specific option types.
/// ///
......
// 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 // Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above // purpose with or without fee is hereby granted, provided that the above
...@@ -230,7 +230,7 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) { ...@@ -230,7 +230,7 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
// 1 byte larger than the size of the string // 1 byte larger than the size of the string
// representation of this FQDN. // representation of this FQDN.
data_size = fqdn.size() + 1; data_size = fqdn.size() + 1;
} else { } else if ( (*field == OPT_BINARY_TYPE) || (*field == OPT_STRING_TYPE) ) {
// In other case we are dealing with string or binary value // In other case we are dealing with string or binary value
// which size can't be determined. Thus we consume the // which size can't be determined. Thus we consume the
// remaining part of the buffer for it. Note that variable // remaining part of the buffer for it. Note that variable
...@@ -238,14 +238,11 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) { ...@@ -238,14 +238,11 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
// that the validate() function in OptionDefinition object // that the validate() function in OptionDefinition object
// should have checked wheter it is a case for this option. // should have checked wheter it is a case for this option.
data_size = std::distance(data, data_buf.end()); data_size = std::distance(data, data_buf.end());
} } else {
if (data_size == 0) {
// If we reached the end of buffer we assume that this option is // If we reached the end of buffer we assume that this option is
// truncated because there is no remaining data to initialize // truncated because there is no remaining data to initialize
// an option field. // an option field.
if (data_size == 0) { isc_throw(OutOfRange, "option buffer truncated");
isc_throw(OutOfRange, "option buffer truncated");
}
} }
} else { } else {
// Our data field requires that there is a certain chunk of // Our data field requires that there is a certain chunk of
......
// 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 // Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above // purpose with or without fee is hereby granted, provided that the above
...@@ -21,10 +21,17 @@ ...@@ -21,10 +21,17 @@
#include <sstream> #include <sstream>
using namespace std; using namespace std;
using namespace isc::asiolink;
namespace isc { namespace isc {
namespace dhcp { namespace dhcp {
Pkt6::RelayInfo::RelayInfo()
:msg_type_(0), hop_count_(0), linkaddr_("::"), peeraddr_("::"), relay_msg_len_(0) {
// interface_id_, subscriber_id_, remote_id_ initialized to NULL
// echo_options_ initialized to empty collection
}
Pkt6::Pkt6(const uint8_t* buf, uint32_t buf_len, DHCPv6Proto proto /* = UDP */) : Pkt6::Pkt6(const uint8_t* buf, uint32_t buf_len, DHCPv6Proto proto /* = UDP */) :
proto_(proto), proto_(proto),
msg_type_(0), msg_type_(0),
...@@ -54,9 +61,61 @@ Pkt6::Pkt6(uint8_t msg_type, uint32_t transid, DHCPv6Proto proto /*= UDP*/) : ...@@ -54,9 +61,61 @@ Pkt6::Pkt6(uint8_t msg_type, uint32_t transid, DHCPv6Proto proto /*= UDP*/) :
} }
uint16_t Pkt6::len() { uint16_t Pkt6::len() {
if (relay_info_.empty()) {
return (directLen());
} else {
// Unfortunately we need to re-calculate relay size every time, because
// we need to make sure that once a new option is added, its extra size
// is reflected in Pkt6::len().
calculateRelaySizes();
return (relay_info_[0].relay_msg_len_ + getRelayOverhead(relay_info_[0]));
}
}
OptionPtr Pkt6::getRelayOption(uint16_t opt_type, uint8_t relay_level) {
if (relay_level >= relay_info_.size()) {
isc_throw(OutOfRange, "This message was relayed " << relay_info_.size() << " time(s)."
<< " There is no info about " << relay_level + 1 << " relay.");
}
for (Option::OptionCollection::iterator it = relay_info_[relay_level].options_.begin();
it != relay_info_[relay_level].options_.end(); ++it) {
if ((*it).second->getType() == opt_type) {
return (it->second);
}
}
return (OptionPtr());
}
uint16_t Pkt6::getRelayOverhead(const RelayInfo& relay) const {
uint16_t len = DHCPV6_RELAY_HDR_LEN // fixed header
+ Option::OPTION6_HDR_LEN; // header of the relay-msg option
for (Option::OptionCollection::const_iterator opt = relay.options_.begin();
opt != relay.options_.end(); ++opt) {
len += (opt->second)->len();
}
return (len);
}
uint16_t Pkt6::calculateRelaySizes() {
uint16_t len = directLen(); // start with length of all options
for (int relay_index = relay_info_.size(); relay_index > 0; --relay_index) {
relay_info_[relay_index - 1].relay_msg_len_ = len;
len += getRelayOverhead(relay_info_[relay_index - 1]);
}
return (len);
}
uint16_t Pkt6::directLen() const {
uint16_t length = DHCPV6_PKT_HDR_LEN; // DHCPv6 header uint16_t length = DHCPV6_PKT_HDR_LEN; // DHCPv6 header
for (Option::OptionCollection::iterator it = options_.begin(); for (Option::OptionCollection::const_iterator it = options_.begin();
it != options_.end(); it != options_.end();
++it) { ++it) {
length += (*it).second->len(); length += (*it).second->len();
...@@ -82,6 +141,50 @@ Pkt6::pack() { ...@@ -82,6 +141,50 @@ Pkt6::pack() {
bool bool
Pkt6::packUDP() { Pkt6::packUDP() {
try { try {
// is this a relayed packet?
if (!relay_info_.empty()) {
// calculate size needed for each relay (if there is only one relay,
// then it will be equal to "regular" length + relay-forw header +
// size of relay-msg option header + possibly size of interface-id
// option (if present). If there is more than one relay, the whole
// process is called iteratively for each relay.
calculateRelaySizes();
// Now for each relay, we need to...
for (vector<RelayInfo>::iterator relay = relay_info_.begin();
relay != relay_info_.end(); ++relay) {
// build relay-forw/relay-repl header (see RFC3315, section 7)
bufferOut_.writeUint8(relay->msg_type_);
bufferOut_.writeUint8(relay->hop_count_);
bufferOut_.writeData(&(relay->linkaddr_.toBytes()[0]),
isc::asiolink::V6ADDRESS_LEN);
bufferOut_.writeData(&relay->peeraddr_.toBytes()[0],
isc::asiolink::V6ADDRESS_LEN);
// store every option in this relay scope. Usually that will be
// only interface-id, but occasionally other options may be
// present here as well (vendor-opts for Cable modems,
// subscriber-id, remote-id, options echoed back from Echo
// Request Option, etc.)
for (Option::OptionCollection::const_iterator opt =
relay->options_.begin();
opt != relay->options_.end(); ++opt) {
(opt->second)->pack(bufferOut_);
}
// and include header relay-msg option. Its payload will be
// generated in the next iteration (if there are more relays)
// or outside the loop (if there are no more relays and the
// payload is a direct message)
bufferOut_.writeUint16(D6O_RELAY_MSG);
bufferOut_.writeUint16(relay->relay_msg_len_);
}
}
// DHCPv6 header: message-type (1 octect) + transaction id (3 octets) // DHCPv6 header: message-type (1 octect) + transaction id (3 octets)
bufferOut_.writeUint8(msg_type_); bufferOut_.writeUint8(msg_type_);
// store 3-octet transaction-id // store 3-octet transaction-id
...@@ -127,12 +230,43 @@ Pkt6::unpackUDP() { ...@@ -127,12 +230,43 @@ Pkt6::unpackUDP() {
return (false); return (false);
} }
msg_type_ = data_[0]; msg_type_ = data_[0];
transid_ = ( (data_[1]) << 16 ) + switch (msg_type_) {
((data_[2]) << 8) + (data_[3]); case DHCPV6_SOLICIT:
case DHCPV6_ADVERTISE:
case DHCPV6_REQUEST:
case DHCPV6_CONFIRM:
case DHCPV6_RENEW:
case DHCPV6_REBIND:
case DHCPV6_REPLY:
case DHCPV6_DECLINE:
case DHCPV6_RECONFIGURE:
case DHCPV6_INFORMATION_REQUEST:
default: // assume that uknown messages are not using relay format
{
return (unpackMsg(data_.begin(), data_.end()));
}
case DHCPV6_RELAY_FORW:
case DHCPV6_RELAY_REPL:
return (unpackRelayMsg());
}
}
bool
Pkt6::unpackMsg(OptionBuffer::const_iterator begin,
OptionBuffer::const_iterator end) {
if (std::distance(begin, end) < 4) {
// truncated message (less than 4 bytes)
return (false);
}
msg_type_ = *begin++;
transid_ = ( (*begin++) << 16 ) +
((*begin++) << 8) + (*begin++);
transid_ = transid_ & 0xffffff; transid_ = transid_ & 0xffffff;
try { try {
OptionBuffer opt_buffer(data_.begin() + 4, data_.end()); OptionBuffer opt_buffer(begin, end);
LibDHCP::unpackOptions6(opt_buffer, options_); LibDHCP::unpackOptions6(opt_buffer, options_);
} catch (const Exception& e) { } catch (const Exception& e) {
...@@ -142,6 +276,97 @@ Pkt6::unpackUDP() { ...@@ -142,6 +276,97 @@ Pkt6::unpackUDP() {
return (true); return (true);
} }
bool
Pkt6::unpackRelayMsg() {
// we use offset + bufsize, because we want to avoid creating unnecessary
// copies. There may be up to 32 relays. While using InputBuffer would
// be probably a bit cleaner, copying data up to 32 times is unacceptable
// price here. Hence a single buffer with offets and lengths.
size_t bufsize = data_.size();
size_t offset = 0;
while (bufsize >= DHCPV6_RELAY_HDR_LEN) {
RelayInfo relay;
size_t relay_msg_offset = 0;
size_t relay_msg_len = 0;
// parse fixed header first (first 34 bytes)
relay.msg_type_ = data_[offset++];
relay.hop_count_ = data_[offset++];
relay.linkaddr_ = IOAddress::fromBytes(AF_INET6, &data_[offset]);
offset += isc::asiolink::V6ADDRESS_LEN;
relay.peeraddr_ = IOAddress::fromBytes(AF_INET6, &data_[offset]);
offset += isc::asiolink::V6ADDRESS_LEN;
bufsize -= DHCPV6_RELAY_HDR_LEN; // 34 bytes (1+1+16+16)
try {
// parse the rest as options
OptionBuffer opt_buffer(&data_[offset], &data_[offset+bufsize]);
LibDHCP::unpackOptions6(opt_buffer, relay.options_, &relay_msg_offset,
&relay_msg_len);
/// @todo: check that each option appears at most once
//relay.interface_id_ = options->getOption(D6O_INTERFACE_ID);
//relay.subscriber_id_ = options->getOption(D6O_SUBSCRIBER_ID);
//relay.remote_id_ = options->getOption(D6O_REMOTE_ID);
if (relay_msg_offset == 0 || relay_msg_len == 0) {
isc_throw(BadValue, "Mandatory relay-msg option missing");
}
// store relay information parsed so far
addRelayInfo(relay);
/// @todo: implement ERO here
if (relay_msg_len >= bufsize) {
// length of the relay_msg option extends beyond end of the message
isc_throw(Unexpected, "Relay-msg option is truncated.");
return false;
}
uint8_t inner_type = data_[offset + relay_msg_offset];
offset += relay_msg_offset; // offset is relative
bufsize = relay_msg_len; // length is absolute
if ( (inner_type != DHCPV6_RELAY_FORW) &&
(inner_type != DHCPV6_RELAY_REPL)) {
// Ok, the inner message is not encapsulated, let's decode it
// directly
return (unpackMsg(data_.begin() + offset, data_.begin() + offset
+ relay_msg_len));
}
// Oh well, there's inner relay-forw or relay-repl inside. Let's
// unpack it as well. The next loop iteration will take care
// of that.
} catch (const Exception& e) {
/// @todo: throw exception here once we turn this function to void.
return (false);
}
}
if ( (offset == data_.size()) && (bufsize == 0) ) {
// message has been parsed completely
return (true);
}
/// @todo: log here that there are additional unparsed bytes
return (true);
}
void
Pkt6::addRelayInfo(const RelayInfo& relay) {
if (relay_info_.size() > 32) {
isc_throw(BadValue, "Massage cannot be encapsulated more than 32 times");
}
/// @todo: Implement type checks here (e.g. we could receive relay-forw in relay-repl)
relay_info_.push_back(relay);
}
bool bool
Pkt6::unpackTCP() { Pkt6::unpackTCP() {
isc_throw(Unexpected, "DHCPv6 over TCP (bulk leasequery and failover) " isc_throw(Unexpected, "DHCPv6 over TCP (bulk leasequery and failover) "
......
// 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 // Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above // purpose with or without fee is hereby granted, provided that the above
...@@ -32,15 +32,40 @@ namespace dhcp { ...@@ -32,15 +32,40 @@ namespace dhcp {
class Pkt6 { class Pkt6 {
public: public:
/// specifes DHCPv6 packet header length /// specifies non-relayed DHCPv6 packet header length (over UDP)
const static size_t DHCPV6_PKT_HDR_LEN = 4; const static size_t DHCPV6_PKT_HDR_LEN = 4;
/// specifies relay DHCPv6 packet header length (over UDP)
const static size_t DHCPV6_RELAY_HDR_LEN = 34;
/// DHCPv6 transport protocol /// DHCPv6 transport protocol
enum DHCPv6Proto { enum DHCPv6Proto {
UDP = 0, // most packets are UDP UDP = 0, // most packets are UDP
TCP = 1 // there are TCP DHCPv6 packets (bulk leasequery, failover) TCP = 1 // there are TCP DHCPv6 packets (bulk leasequery, failover)
}; };
/// @brief structure that describes a single relay information
///
/// Client sends messages. Each relay along its way will encapsulate the message.
/// This structure represents all information added by a single relay.
struct RelayInfo {
/// @brief default constructor
RelayInfo();
uint8_t msg_type_; ///< message type (RELAY-FORW oro RELAY-REPL)
uint8_t hop_count_; ///< number of traversed relays (up to 32)
isc::asiolink::IOAddress linkaddr_;///< fixed field in relay-forw/relay-reply
isc::asiolink::IOAddress peeraddr_;///< fixed field in relay-forw/relay-reply
/// @brief length of the relay_msg_len
/// Used when calculating length during pack/unpack
uint16_t relay_msg_len_;
/// options received from a specified relay, except relay-msg option
isc::dhcp::Option::OptionCollection options_;
};
/// Constructor, used in replying to a message /// Constructor, used in replying to a message
/// ///
/// @param msg_type type of message (SOLICIT=1, ADVERTISE=2, ...) /// @param msg_type type of message (SOLICIT=1, ADVERTISE=2, ...)
...@@ -89,7 +114,6 @@ public: ...@@ -89,7 +114,6 @@ public:
/// @return reference to output buffer /// @return reference to output buffer
const isc::util::OutputBuffer& getBuffer() const { return (bufferOut_); }; const isc::util::OutputBuffer& getBuffer() const { return (bufferOut_); };
/// @brief Returns reference to input buffer. /// @brief Returns reference to input buffer.
/// ///
/// @return reference to input buffer /// @return reference to input buffer
...@@ -160,6 +184,23 @@ public: ...@@ -160,6 +184,23 @@ public:
/// @return pointer to found option (or NULL) /// @return pointer to found option (or NULL)
OptionPtr getOption(uint16_t type); OptionPtr getOption(uint16_t type);
/// @brief returns option inserted by relay
///
/// Returns an option from specified relay scope (inserted by a given relay
/// if this is received packet or to be decapsulated by a given relay if
/// this is a transmitted packet). nesting_level specifies which relay
/// scope is to be used. 0 is the outermost encapsulation (relay closest to
/// the server). pkt->relay_info_.size() - 1 is the innermost encapsulation
/// (relay closest to the client).
///
/// @throw isc::OutOfRange if nesting level has invalid value.
///
/// @param option_code code of the requested option
/// @param nesting_level see description above
///
/// @return pointer to the option (or NULL if there is no such option)
OptionPtr getRelayOption(uint16_t option_code, uint8_t nesting_level);
/// @brief Returns all instances of specified type. /// @brief Returns all instances of specified type.
/// ///
/// Returns all instances of options of the specified type. DHCPv6 protocol /// Returns all instances of options of the specified type. DHCPv6 protocol
...@@ -246,7 +287,7 @@ public: ...@@ -246,7 +287,7 @@ public:
/// @brief Returns packet timestamp. /// @brief Returns packet timestamp.
/// ///
/// Returns packet timestamp value updated when /// Returns packet timestamp value updated when
/// packet is received or send. /// packet is received or sent.
/// ///
/// @return packet timestamp. /// @return packet timestamp.
const boost::posix_time::ptime& getTimestamp() const { return timestamp_; } const boost::posix_time::ptime& getTimestamp() const { return timestamp_; }
...@@ -259,8 +300,18 @@ public: ...@@ -259,8 +300,18 @@ public:
/// @return interface name /// @return interface name
void setIface(const std::string& iface ) { iface_ = iface; }; void setIface(const std::string& iface ) { iface_ = iface; };
/// @brief add information about one traversed relay
///
/// This adds information about one traversed relay, i.e.
/// one relay-forw or relay-repl level of encapsulation.
///
/// @param relay structure with necessary relay information
void addRelayInfo(const RelayInfo& relay);
/// collection of options present in this message /// collection of options present in this message
/// ///
/// @todo: Text mentions protected, but this is really public
///
/// @warning This protected member is accessed by derived /// @warning This protected member is accessed by derived
/// classes directly. One of such derived classes is /// classes directly. One of such derived classes is
/// @ref perfdhcp::PerfPkt6. The impact on derived clasess' /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
...@@ -305,6 +356,15 @@ public: ...@@ -305,6 +356,15 @@ public:
/// be freed by the caller. /// be freed by the caller.
const char* getName() const;