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
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
// purpose with or without fee is hereby granted, provided that the above
......@@ -128,7 +128,9 @@ LibDHCP::optionFactory(Option::Universe u,
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 length = buf.size();
......@@ -143,6 +145,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
while (offset + 4 <= length) {
uint16_t opt_type = isc::util::readUint16(&buf[offset]);
offset += 2;
uint16_t opt_len = isc::util::readUint16(&buf[offset]);
offset += 2;
......@@ -151,6 +154,16 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
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
// code is non-unique within this container however at this point we
// expect to get one option definition with the particular code. If more
......
// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
......@@ -115,14 +115,27 @@ public:
/// @brief Parses provided buffer as DHCPv6 options and creates Option objects.
///
/// Parses provided buffer and stores created Option objects
/// in options container.
/// Parses provided buffer and stores created Option objects in options
/// 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 options Reference to option container. Options will be
/// 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,
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.
///
......
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
......@@ -230,7 +230,7 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
// 1 byte larger than the size of the string
// representation of this FQDN.
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
// which size can't be determined. Thus we consume the
// remaining part of the buffer for it. Note that variable
......@@ -238,15 +238,12 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
// that the validate() function in OptionDefinition object
// should have checked wheter it is a case for this option.
data_size = std::distance(data, data_buf.end());
}
if (data_size == 0) {
} else {
// If we reached the end of buffer we assume that this option is
// truncated because there is no remaining data to initialize
// an option field.
if (data_size == 0) {
isc_throw(OutOfRange, "option buffer truncated");
}
}
} else {
// Our data field requires that there is a certain chunk of
// data left in the buffer. If not, option is truncated.
......
// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
......@@ -21,10 +21,17 @@
#include <sstream>
using namespace std;
using namespace isc::asiolink;
namespace isc {
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 */) :
proto_(proto),
msg_type_(0),
......@@ -54,9 +61,61 @@ Pkt6::Pkt6(uint8_t msg_type, uint32_t transid, DHCPv6Proto proto /*= UDP*/) :
}
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
for (Option::OptionCollection::iterator it = options_.begin();
for (Option::OptionCollection::const_iterator it = options_.begin();
it != options_.end();
++it) {
length += (*it).second->len();
......@@ -82,6 +141,50 @@ Pkt6::pack() {
bool
Pkt6::packUDP() {
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)
bufferOut_.writeUint8(msg_type_);
// store 3-octet transaction-id
......@@ -127,12 +230,43 @@ Pkt6::unpackUDP() {
return (false);
}
msg_type_ = data_[0];
transid_ = ( (data_[1]) << 16 ) +
((data_[2]) << 8) + (data_[3]);
switch (msg_type_) {
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;
try {
OptionBuffer opt_buffer(data_.begin() + 4, data_.end());
OptionBuffer opt_buffer(begin, end);
LibDHCP::unpackOptions6(opt_buffer, options_);
} catch (const Exception& e) {
......@@ -142,6 +276,97 @@ Pkt6::unpackUDP() {
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
Pkt6::unpackTCP() {
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
// purpose with or without fee is hereby granted, provided that the above
......@@ -32,15 +32,40 @@ namespace dhcp {
class Pkt6 {
public:
/// specifes DHCPv6 packet header length
/// specifies non-relayed DHCPv6 packet header length (over UDP)
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
enum DHCPv6Proto {
UDP = 0, // most packets are UDP
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
///
/// @param msg_type type of message (SOLICIT=1, ADVERTISE=2, ...)
......@@ -89,7 +114,6 @@ public:
/// @return reference to output buffer
const isc::util::OutputBuffer& getBuffer() const { return (bufferOut_); };
/// @brief Returns reference to input buffer.
///
/// @return reference to input buffer
......@@ -160,6 +184,23 @@ public:
/// @return pointer to found option (or NULL)
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.
///
/// Returns all instances of options of the specified type. DHCPv6 protocol
......@@ -246,7 +287,7 @@ public:
/// @brief Returns packet timestamp.
///
/// Returns packet timestamp value updated when
/// packet is received or send.
/// packet is received or sent.
///
/// @return packet timestamp.
const boost::posix_time::ptime& getTimestamp() const { return timestamp_; }
......@@ -259,8 +300,18 @@ public:
/// @return interface name
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
///
/// @todo: Text mentions protected, but this is really public
///
/// @warning This protected member is accessed by derived
/// classes directly. One of such derived classes is
/// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
......@@ -305,6 +356,15 @@ public:
/// be freed by the caller.
const char* getName() const;
/// relay information
///
/// this is a public field. Otherwise we hit one of the two problems:
/// we return reference to an internal field (and that reference could
/// be potentially used past Pkt6 object lifetime causing badness) or
/// we return a copy (which is inefficient and also causes any updates
/// to be impossible). Therefore public field is considered the best
/// (or least bad) solution.
std::vector<RelayInfo> relay_info_;
protected:
/// Builds on wire packet for TCP transmission.
///
......@@ -340,6 +400,44 @@ protected:
/// @return true, if build was successful
bool unpackUDP();
/// @brief unpacks direct (non-relayed) message
///
/// This method unpacks specified buffer range as a direct
/// (e.g. solicit or request) message. This method is called from
/// unpackUDP() when received message is detected to be direct.
///
/// @param begin start of the buffer
/// @param end end of the buffer
/// @return true if parsing was successful and there are no leftover bytes
bool unpackMsg(OptionBuffer::const_iterator begin,
OptionBuffer::const_iterator end);
/// @brief unpacks relayed message (RELAY-FORW or RELAY-REPL)
///
/// This method is called from unpackUDP() when received message
/// is detected to be relay-message. It goes iteratively over
/// all relays (if there are multiple encapsulation levels).
///
/// @return true if parsing was successful
bool unpackRelayMsg();
/// @brief calculates overhead introduced in specified relay
///
/// It is used when calculating message size and packing message
/// @param relay RelayInfo structure that holds information about relay
/// @return number of bytes needed to store relay information
uint16_t getRelayOverhead(const RelayInfo& relay) const;
/// @brief calculates overhead for all relays defined for this message
/// @return number of bytes needed to store all relay information
uint16_t calculateRelaySizes();
/// @brief calculates size of the message as if it was not relayed at all
///
/// This is equal to len() if the message was not relayed.
/// @return number of bytes required to store the message
uint16_t directLen() const;
/// UDP (usually) or TCP (bulk leasequery or failover)
DHCPv6Proto proto_;
......
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
......@@ -766,9 +766,15 @@ TEST_F(OptionCustomTest, recordDataTruncated) {
// 2 bytes of uint16_t value and IPv6 address. Option definitions specifies
// 3 data fields for this option but the length of the data is insufficient
// to initialize 3 data field.
EXPECT_THROW(
option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 18)),
isc::OutOfRange
// @todo:
// Currently the code was modified to allow empty string or empty binary data
// Potentially change this back to EXPECT_THROW(..., OutOfRange) once we
// decide how to treat zero length strings and binary data (they are typically
// valid or invalid on a per option basis, so there likely won't be a single
// one answer to all)
EXPECT_NO_THROW(
option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 18))
);
// Try to further reduce the length of the buffer to make it insufficient
......
// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
......@@ -17,9 +17,15 @@
#include <asiolink/io_address.h>
#include <dhcp/dhcp6.h>
#include <dhcp/option.h>
#include <dhcp/option_custom.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <dhcp/pkt6.h>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/scoped_ptr.hpp>
#include <util/encode/hex.h>
#include <gtest/gtest.h>
#include <iostream>
......@@ -99,6 +105,67 @@ Pkt6* capture1() {
return (pkt);
}
/// @brief creates doubly relayed solicit message
///
/// This is a traffic capture exported from wireshark. It includes a SOLICIT
/// message that passed through two relays. Each relay include interface-id,
/// remote-id and relay-forw encapsulation. It is especially interesting,
/// because of the following properties:
/// - double encapsulation
/// - first relay inserts relay-msg before extra options
/// - second relay inserts relay-msg after extra options
/// - both relays are from different vendors
/// - interface-id are different for each relay
/// - first relay inserts valid remote-id
/// - second relay inserts remote-id with empty vendor data
/// - the solicit message requests for custom options in ORO
/// - there are option types in RELAY-FORW that do not appear in SOLICIT
/// - there are option types in SOLICT that do not appear in RELAY-FORW