2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-30 05:27:55 +00:00

[3180] Implemented support for callback functions to parse options.

This commit is contained in:
Marcin Siodelski 2013-10-04 21:17:07 +02:00
parent 23c944640d
commit d06b0d68f9
27 changed files with 1020 additions and 278 deletions

View File

@ -35,6 +35,7 @@
#include <hooks/hooks_manager.h>
#include <boost/algorithm/string/erase.hpp>
#include <boost/bind.hpp>
#include <iomanip>
#include <fstream>
@ -197,6 +198,15 @@ Dhcpv4Srv::run() {
continue;
}
// In order to parse the DHCP options, the server needs to use some
// configuration information such as: existing option spaces, option
// definitions etc. This is the kind of information which is not
// available in the libdhcp, so we need to supply our own implementation
// of the option parsing function here, which would rely on the
// configuration data.
query->setCallback(boost::bind(&Dhcpv4Srv::unpackOptions, this,
_1, _2, _3));
bool skip_unpack = false;
// The packet has just been received so contains the uninterpreted wire
@ -1164,6 +1174,95 @@ Dhcpv4Srv::openActiveSockets(const uint16_t port,
}
}
size_t
Dhcpv4Srv::unpackOptions(const OptionBuffer& buf,
const std::string& option_space,
isc::dhcp::OptionCollection& options) {
size_t offset = 0;
OptionDefContainer option_defs;
if (option_space == "dhcp6") {
// Get the list of stdandard option definitions.
option_defs = LibDHCP::getOptionDefs(Option::V6);
} else if (!option_space.empty()) {
OptionDefContainerPtr option_defs_ptr =
CfgMgr::instance().getOptionDefs(option_space);
if (option_defs_ptr != NULL) {
option_defs = *option_defs_ptr;
}
}
// Get the search index #1. It allows to search for option definitions
// using option code.
const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
// The buffer being read comprises a set of options, each starting with
// a one-byte type code and a one-byte length field.
while (offset + 1 <= buf.size()) {
uint8_t opt_type = buf[offset++];
// DHO_END is a special, one octet long option
if (opt_type == DHO_END)
return (offset); // just return. Don't need to add DHO_END option
// DHO_PAD is just a padding after DHO_END. Let's continue parsing
// in case we receive a message without DHO_END.
if (opt_type == DHO_PAD)
continue;
if (offset + 1 >= buf.size()) {
// opt_type must be cast to integer so as it is not treated as
// unsigned char value (a number is presented in error message).
isc_throw(OutOfRange, "Attempt to parse truncated option "
<< static_cast<int>(opt_type));
}
uint8_t opt_len = buf[offset++];
if (offset + opt_len > buf.size()) {
isc_throw(OutOfRange, "Option parse failed. Tried to parse "
<< offset + opt_len << " bytes from " << buf.size()
<< "-byte long buffer.");
}
// 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 are
// returned we report an error.
const OptionDefContainerTypeRange& range = idx.equal_range(opt_type);
// Get the number of returned option definitions for the option code.
size_t num_defs = distance(range.first, range.second);
OptionPtr opt;
if (num_defs > 1) {
// Multiple options of the same code are not supported right now!
isc_throw(isc::Unexpected, "Internal error: multiple option definitions"
" for option type " << static_cast<int>(opt_type)
<< " returned. Currently it is not supported to initialize"
<< " multiple option definitions for the same option code."
<< " This will be supported once support for option spaces"
<< " is implemented");
} else if (num_defs == 0) {
opt = OptionPtr(new Option(Option::V4, opt_type,
buf.begin() + offset,
buf.begin() + offset + opt_len));
opt->setEncapsulatedSpace("dhcp4");
} else {
// The option definition has been found. Use it to create
// the option instance from the provided buffer chunk.
const OptionDefinitionPtr& def = *(range.first);
assert(def);
opt = def->optionFactory(Option::V4, opt_type,
buf.begin() + offset,
buf.begin() + offset + opt_len,
boost::bind(&Dhcpv4Srv::unpackOptions,
this, _1, _2, _3));
}
options.insert(std::make_pair(opt_type, opt));
offset += opt_len;
}
return (offset);
}
} // namespace dhcp
} // namespace isc

View File

@ -351,6 +351,18 @@ protected:
private:
/// @brief Implements a callback function to parse options in the message.
///
/// @param buf a A buffer holding options in on-wire format.
/// @param option_space A name of the option space which holds definitions
/// of to be used to parse options in the packets.
/// @param [out] options A reference to the collection where parsed options
/// will be stored.
/// @return An offset to the first byte after last parsed option.
size_t unpackOptions(const OptionBuffer& buf,
const std::string& option_space,
isc::dhcp::OptionCollection& options);
/// @brief Constructs netmask option based on subnet4
/// @param subnet subnet for which the netmask will be calculated
///

View File

@ -229,6 +229,15 @@ bool Dhcpv6Srv::run() {
continue;
}
// In order to parse the DHCP options, the server needs to use some
// configuration information such as: existing option spaces, option
// definitions etc. This is the kind of information which is not
// available in the libdhcp, so we need to supply our own implementation
// of the option parsing function here, which would rely on the
// configuration data.
query->setCallback(boost::bind(&Dhcpv6Srv::unpackOptions, this, _1, _2,
_3, _4, _5));
bool skip_unpack = false;
// The packet has just been received so contains the uninterpreted wire
@ -703,7 +712,7 @@ Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
void
Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
RequirementLevel serverid) {
Option::OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
switch (clientid) {
case MANDATORY:
if (client_ids.size() != 1) {
@ -724,7 +733,7 @@ Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
break;
}
Option::OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
switch (serverid) {
case FORBIDDEN:
if (!server_ids.empty()) {
@ -870,7 +879,7 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
//
// @todo: expand this to cover IA_PD and IA_TA once we implement support for
// prefix delegation and temporary addresses.
for (Option::OptionCollection::iterator opt = question->options_.begin();
for (OptionCollection::iterator opt = question->options_.begin();
opt != question->options_.end(); ++opt) {
switch (opt->second->getType()) {
case D6O_IA_NA: {
@ -1052,8 +1061,8 @@ Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
// Get all IAs from the answer. For each IA, holding an address we will
// create a corresponding NameChangeRequest.
Option::OptionCollection answer_ias = answer->getOptions(D6O_IA_NA);
for (Option::OptionCollection::const_iterator answer_ia =
OptionCollection answer_ias = answer->getOptions(D6O_IA_NA);
for (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.
@ -1493,7 +1502,7 @@ Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply,
}
DuidPtr duid(new DUID(opt_duid->getData()));
for (Option::OptionCollection::iterator opt = renew->options_.begin();
for (OptionCollection::iterator opt = renew->options_.begin();
opt != renew->options_.end(); ++opt) {
switch (opt->second->getType()) {
case D6O_IA_NA: {
@ -1543,7 +1552,7 @@ Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
DuidPtr duid(new DUID(opt_duid->getData()));
int general_status = STATUS_Success;
for (Option::OptionCollection::iterator opt = release->options_.begin();
for (OptionCollection::iterator opt = release->options_.begin();
opt != release->options_.end(); ++opt) {
switch (opt->second->getType()) {
case D6O_IA_NA: {
@ -1868,5 +1877,99 @@ Dhcpv6Srv::openActiveSockets(const uint16_t port) {
}
}
size_t
Dhcpv6Srv::unpackOptions(const OptionBuffer& buf,
const std::string& option_space,
isc::dhcp::OptionCollection& options,
size_t* relay_msg_offset,
size_t* relay_msg_len) {
size_t offset = 0;
size_t length = buf.size();
OptionDefContainer option_defs;
if (option_space == "dhcp6") {
// Get the list of stdandard option definitions.
option_defs = LibDHCP::getOptionDefs(Option::V6);
} else if (!option_space.empty()) {
OptionDefContainerPtr option_defs_ptr =
CfgMgr::instance().getOptionDefs(option_space);
if (option_defs_ptr != NULL) {
option_defs = *option_defs_ptr;
}
}
// Get the search index #1. It allows to search for option definitions
// using option code.
const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
// The buffer being read comprises a set of options, each starting with
// a two-byte type code and a two-byte length field.
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;
if (offset + opt_len > length) {
// @todo: consider throwing exception here.
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
// are returned we report an error.
const OptionDefContainerTypeRange& range = idx.equal_range(opt_type);
// Get the number of returned option definitions for the option code.
size_t num_defs = distance(range.first, range.second);
OptionPtr opt;
if (num_defs > 1) {
// Multiple options of the same code are not supported right now!
isc_throw(isc::Unexpected, "Internal error: multiple option definitions"
" for option type " << opt_type << " returned. Currently it is not"
" supported to initialize multiple option definitions"
" for the same option code. This will be supported once"
" support for option spaces is implemented");
} else if (num_defs == 0) {
// @todo Don't crash if definition does not exist because only a few
// option definitions are initialized right now. In the future
// we will initialize definitions for all options and we will
// remove this elseif. For now, return generic option.
opt = OptionPtr(new Option(Option::V6, opt_type,
buf.begin() + offset,
buf.begin() + offset + opt_len));
opt->setEncapsulatedSpace("dhcp6");
} else {
// The option definition has been found. Use it to create
// the option instance from the provided buffer chunk.
const OptionDefinitionPtr& def = *(range.first);
assert(def);
opt = def->optionFactory(Option::V6, opt_type,
buf.begin() + offset,
buf.begin() + offset + opt_len,
boost::bind(&Dhcpv6Srv::unpackOptions, this, _1, _2,
_3, _4, _5));
}
// add option to options
options.insert(std::make_pair(opt_type, opt));
offset += opt_len;
}
return (offset);
}
};
};

View File

@ -459,6 +459,24 @@ protected:
/// simulates transmission of a packet. For that purpose it is protected.
virtual void sendPacket(const Pkt6Ptr& pkt);
/// @brief Implements a callback function to parse options in the message.
///
/// @param buf a A buffer holding options in on-wire format.
/// @param option_space A name of the option space which holds definitions
/// of to be used to parse options in the packets.
/// @param [out] options A reference to the collection where parsed options
/// will be stored.
/// @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 An offset to the first byte after last parsed option.
size_t unpackOptions(const OptionBuffer& buf,
const std::string& option_space,
isc::dhcp::OptionCollection& options,
size_t* relay_msg_offset,
size_t* relay_msg_len);
private:
/// @brief Allocation Engine.
/// Pointer to the allocation engine that we are currently using

View File

@ -24,6 +24,7 @@
#include <dhcp/option6_client_fqdn.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <dhcp/iface_mgr.h>
#include <dhcp6/config_parser.h>
@ -2157,7 +2158,7 @@ TEST_F(Dhcpv6SrvTest, portsRelayedTraffic) {
// Checks if server is able to handle a relayed traffic from DOCSIS3.0 modems
// @todo Uncomment this test as part of #3180 work.
// Kea code currently fails to handle docsis traffic.
TEST_F(Dhcpv6SrvTest, DISABLED_docsisTraffic) {
TEST_F(Dhcpv6SrvTest, docsisTraffic) {
NakedDhcpv6Srv srv(0);
@ -2181,6 +2182,78 @@ TEST_F(Dhcpv6SrvTest, DISABLED_docsisTraffic) {
/// that is relayed properly, etc.
}
// This test verifies that the following option structure can be parsed:
// - option (option space 'foobar')
// - sub option (option space 'foo')
// - sub option (option space 'bar')
TEST_F(Dhcpv6SrvTest, unpackOptions) {
// Create option definition for each level of encapsulation. Each option
// definition is for the option code 1. Options may have the same
// option code because they belong to different option spaces.
// Top level option encapsulates options which belong to 'space-foo'.
OptionDefinitionPtr opt_def(new OptionDefinition("option-foobar", 1, "uint32",
"space-foo"));\
// Middle option encapsulates options which belong to 'space-bar'
OptionDefinitionPtr opt_def2(new OptionDefinition("option-foo", 1, "uint16",
"space-bar"));
// Low level option doesn't encapsulate any option space.
OptionDefinitionPtr opt_def3(new OptionDefinition("option-bar", 1,
"uint8"));
// Add option definitions to the Configuration Manager. Each goes under
// different option space.
CfgMgr& cfgmgr = CfgMgr::instance();
ASSERT_NO_THROW(cfgmgr.addOptionDef(opt_def, "space-foobar"));
ASSERT_NO_THROW(cfgmgr.addOptionDef(opt_def2, "space-foo"));
ASSERT_NO_THROW(cfgmgr.addOptionDef(opt_def3, "space-bar"));
// Create the buffer holding the structure of options.
const char raw_data[] = {
// First option starts here.
0x00, 0x01, // option code = 1
0x00, 0x0F, // option length = 15
0x00, 0x01, 0x02, 0x03, // This option carries uint32 value
// Sub option starts here.
0x00, 0x01, // option code = 1
0x00, 0x07, // option length = 7
0x01, 0x02, // this option carries uint16 value
// Last option starts here.
0x00, 0x01, // option code = 1
0x00, 0x01, // option length = 1
0x00 // This option carries a single uint8 value and has no sub options.
};
OptionBuffer buf(raw_data, raw_data + sizeof(raw_data));
// Parse options.
NakedDhcpv6Srv srv(0);
OptionCollection options;
ASSERT_NO_THROW(srv.unpackOptions(buf, "space-foobar", options, 0, 0));
// There should be one top level option.
ASSERT_EQ(1, options.size());
boost::shared_ptr<OptionInt<uint32_t> > option_foobar =
boost::dynamic_pointer_cast<OptionInt<uint32_t> >(options.begin()->
second);
ASSERT_TRUE(option_foobar);
EXPECT_EQ(1, option_foobar->getType());
EXPECT_EQ(0x00010203, option_foobar->getValue());
// There should be a middle level option held in option_foobar.
boost::shared_ptr<OptionInt<uint16_t> > option_foo =
boost::dynamic_pointer_cast<OptionInt<uint16_t> >(option_foobar->
getOption(1));
ASSERT_TRUE(option_foo);
EXPECT_EQ(1, option_foo->getType());
EXPECT_EQ(0x0102, option_foo->getValue());
// Finally, there should be a low level option under option_foo.
boost::shared_ptr<OptionInt<uint8_t> > option_bar =
boost::dynamic_pointer_cast<OptionInt<uint8_t> >(option_foo->getOption(1));
ASSERT_TRUE(option_bar);
EXPECT_EQ(1, option_bar->getType());
EXPECT_EQ(0x0, option_bar->getValue());
}
/// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
/// to call processX() methods.

View File

@ -110,6 +110,7 @@ public:
using Dhcpv6Srv::sanityCheck;
using Dhcpv6Srv::loadServerID;
using Dhcpv6Srv::writeServerID;
using Dhcpv6Srv::unpackOptions;
using Dhcpv6Srv::name_change_reqs_;
/// @brief packets we pretend to receive

View File

@ -128,7 +128,7 @@ LibDHCP::optionFactory(Option::Universe u,
size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
isc::dhcp::Option::OptionCollection& options,
isc::dhcp::OptionCollection& options,
size_t* relay_msg_offset /* = 0 */,
size_t* relay_msg_len /* = 0 */) {
size_t offset = 0;
@ -206,7 +206,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
}
size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
isc::dhcp::Option::OptionCollection& options) {
isc::dhcp::OptionCollection& options) {
size_t offset = 0;
// Get the list of stdandard option definitions.
@ -282,8 +282,8 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
void
LibDHCP::packOptions(isc::util::OutputBuffer& buf,
const Option::OptionCollection& options) {
for (Option::OptionCollection::const_iterator it = options.begin();
const OptionCollection& options) {
for (OptionCollection::const_iterator it = options.begin();
it != options.end(); ++it) {
it->second->pack(buf);
}

View File

@ -100,7 +100,7 @@ public:
/// @param buf output buffer (assembled options will be stored here)
/// @param options collection of options to store to
static void packOptions(isc::util::OutputBuffer& buf,
const isc::dhcp::Option::OptionCollection& options);
const isc::dhcp::OptionCollection& options);
/// @brief Parses provided buffer as DHCPv4 options and creates Option objects.
///
@ -111,7 +111,7 @@ public:
/// @param options Reference to option container. Options will be
/// put here.
static size_t unpackOptions4(const OptionBuffer& buf,
isc::dhcp::Option::OptionCollection& options);
isc::dhcp::OptionCollection& options);
/// @brief Parses provided buffer as DHCPv6 options and creates Option objects.
///
@ -133,7 +133,7 @@ public:
/// 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::OptionCollection& options,
size_t* relay_msg_offset = 0,
size_t* relay_msg_len = 0);

View File

@ -126,6 +126,13 @@ void Option::unpack(OptionBufferConstIter begin,
void
Option::unpackOptions(const OptionBuffer& buf) {
// If custom option parsing function has been set, use this function
// to parse options. Otherwise, use standard function from libdhcp++.
if (!callback_.empty()) {
callback_(buf, getEncapsulatedSpace(), options_, 0, 0);
return;
}
switch (universe_) {
case V4:
LibDHCP::unpackOptions4(buf, options_);
@ -146,7 +153,7 @@ uint16_t Option::len() {
int length = getHeaderLen() + data_.size();
// ... and sum of lengths of all suboptions
for (Option::OptionCollection::iterator it = options_.begin();
for (OptionCollection::iterator it = options_.begin();
it != options_.end();
++it) {
length += (*it).second->len();
@ -169,7 +176,7 @@ Option::valid() {
}
OptionPtr Option::getOption(uint16_t opt_type) {
isc::dhcp::Option::OptionCollection::const_iterator x =
isc::dhcp::OptionCollection::const_iterator x =
options_.find(opt_type);
if ( x != options_.end() ) {
return (*x).second;
@ -178,7 +185,7 @@ OptionPtr Option::getOption(uint16_t opt_type) {
}
bool Option::delOption(uint16_t opt_type) {
isc::dhcp::Option::OptionCollection::iterator x = options_.find(opt_type);
isc::dhcp::OptionCollection::iterator x = options_.find(opt_type);
if ( x != options_.end() ) {
options_.erase(x);
return true; // delete successful

View File

@ -17,6 +17,7 @@
#include <util/buffer.h>
#include <boost/function.hpp>
#include <boost/shared_ptr.hpp>
#include <map>
@ -44,6 +45,14 @@ typedef boost::shared_ptr<OptionBuffer> OptionBufferPtr;
class Option;
typedef boost::shared_ptr<Option> OptionPtr;
/// A collection of DHCPv6 options
typedef std::multimap<unsigned int, OptionPtr> OptionCollection;
/// This type describes a callback function to parse options from buffer.
typedef boost::function< size_t(const OptionBuffer&, const std::string,
OptionCollection&, size_t*, size_t*)
> UnpackOptionsCallback;
class Option {
public:
@ -56,8 +65,6 @@ public:
/// defines option universe DHCPv4 or DHCPv6
enum Universe { V4, V6 };
/// a collection of DHCPv6 options
typedef std::multimap<unsigned int, OptionPtr> OptionCollection;
/// @brief a factory function prototype
///
@ -290,6 +297,29 @@ public:
data_.assign(first, last);
}
/// @brief Sets the name of the option space encapsulated by this option.
///
/// @param encapsulated_space name of the option space encapsulated by
/// this option.
void setEncapsulatedSpace(const std::string& encapsulated_space) {
encapsulated_space_ = encapsulated_space;
}
/// @brief Returns the name of the option space encapsulated by this option.
///
/// @return name of the option space encapsulated by this option.
std::string getEncapsulatedSpace() const {
return (encapsulated_space_);
}
/// @brief Set callback function to be used to parse options.
///
/// @param callback An instance of the callback function or NULL to
/// uninstall callback.
void setCallback(UnpackOptionsCallback callback) {
callback_ = callback;
}
/// just to force that every option has virtual dtor
virtual ~Option();
@ -372,6 +402,12 @@ protected:
/// collection for storing suboptions
OptionCollection options_;
/// Name of the option space being encapsulated by this option.
std::string encapsulated_space_;
/// A callback to be called to unpack options from the packet.
UnpackOptionsCallback callback_;
/// @todo probably 2 different containers have to be used for v4 (unique
/// options) and v6 (options with the same type can repeat)
};

View File

@ -1,4 +1,4 @@
// 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
@ -36,6 +36,8 @@ Option6IA::Option6IA(uint16_t type, uint32_t iaid)
isc_throw(BadValue, "Can't use Option6IA for IA_TA as it has "
"a different layout");
}
setEncapsulatedSpace("dhcp6");
}
Option6IA::Option6IA(uint16_t type, OptionBufferConstIter begin,
@ -48,6 +50,8 @@ Option6IA::Option6IA(uint16_t type, OptionBufferConstIter begin,
"a different layout");
}
setEncapsulatedSpace("dhcp6");
unpack(begin, end);
}
@ -113,7 +117,7 @@ uint16_t Option6IA::len() {
OPTION6_IA_LEN /* option content (12) */;
// length of all suboptions
for (Option::OptionCollection::iterator it = options_.begin();
for (OptionCollection::iterator it = options_.begin();
it != options_.end();
++it) {
length += (*it).second->len();

View File

@ -1,4 +1,4 @@
// 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
@ -35,6 +35,7 @@ Option6IAAddr::Option6IAAddr(uint16_t type, const isc::asiolink::IOAddress& addr
uint32_t pref, uint32_t valid)
:Option(V6, type), addr_(addr), preferred_(pref),
valid_(valid) {
setEncapsulatedSpace("dhcp6");
if (!addr.isV6()) {
isc_throw(isc::BadValue, addr_.toText() << " is not an IPv6 address");
}
@ -43,6 +44,7 @@ Option6IAAddr::Option6IAAddr(uint16_t type, const isc::asiolink::IOAddress& addr
Option6IAAddr::Option6IAAddr(uint32_t type, OptionBuffer::const_iterator begin,
OptionBuffer::const_iterator end)
:Option(V6, type), addr_("::") {
setEncapsulatedSpace("dhcp6");
unpack(begin, end);
}
@ -110,7 +112,7 @@ uint16_t Option6IAAddr::len() {
// length of all suboptions
// TODO implement:
// protected: unsigned short Option::lenHelper(int header_size);
for (Option::OptionCollection::iterator it = options_.begin();
for (OptionCollection::iterator it = options_.begin();
it != options_.end();
++it) {
length += (*it).second->len();

View File

@ -34,6 +34,7 @@ namespace dhcp {
Option6IAPrefix::Option6IAPrefix(uint16_t type, const isc::asiolink::IOAddress& prefix,
uint8_t prefix_len, uint32_t pref, uint32_t valid)
:Option6IAAddr(type, prefix, pref, valid), prefix_len_(prefix_len) {
setEncapsulatedSpace("dhcp6");
// Option6IAAddr will check if prefix is IPv6 and will throw if it is not
if (prefix_len > 128) {
isc_throw(BadValue, prefix_len << " is not a valid prefix length. "
@ -44,6 +45,7 @@ Option6IAPrefix::Option6IAPrefix(uint16_t type, const isc::asiolink::IOAddress&
Option6IAPrefix::Option6IAPrefix(uint32_t type, OptionBuffer::const_iterator begin,
OptionBuffer::const_iterator end)
:Option6IAAddr(type, begin, end) {
setEncapsulatedSpace("dhcp6");
unpack(begin, end);
}
@ -113,7 +115,7 @@ uint16_t Option6IAPrefix::len() {
uint16_t length = OPTION6_HDR_LEN + OPTION6_IAPREFIX_LEN;
// length of all suboptions
for (Option::OptionCollection::const_iterator it = options_.begin();
for (OptionCollection::const_iterator it = options_.begin();
it != options_.end(); ++it) {
length += (*it).second->len();
}

View File

@ -24,6 +24,7 @@ OptionCustom::OptionCustom(const OptionDefinition& def,
Universe u)
: Option(u, def.getCode(), OptionBuffer()),
definition_(def) {
setEncapsulatedSpace(def.getEncapsulatedSpace());
createBuffers();
}
@ -32,6 +33,7 @@ OptionCustom::OptionCustom(const OptionDefinition& def,
const OptionBuffer& data)
: Option(u, def.getCode(), data.begin(), data.end()),
definition_(def) {
setEncapsulatedSpace(def.getEncapsulatedSpace());
createBuffers(getData());
}
@ -41,6 +43,7 @@ OptionCustom::OptionCustom(const OptionDefinition& def,
OptionBufferConstIter last)
: Option(u, def.getCode(), first, last),
definition_(def) {
setEncapsulatedSpace(def.getEncapsulatedSpace());
createBuffers(getData());
}
@ -522,7 +525,7 @@ OptionCustom::len() {
}
// ... and lengths of all suboptions
for (OptionCustom::OptionCollection::iterator it = options_.begin();
for (OptionCollection::iterator it = options_.begin();
it != options_.end();
++it) {
length += (*it).second->len();

View File

@ -113,7 +113,8 @@ OptionDefinition::addRecordField(const OptionDataType data_type) {
OptionPtr
OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
OptionBufferConstIter begin,
OptionBufferConstIter end) const {
OptionBufferConstIter end,
UnpackOptionsCallback callback) const {
try {
switch(type_) {
case OPT_EMPTY_TYPE:
@ -124,31 +125,37 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
case OPT_UINT8_TYPE:
return (array_type_ ? factoryGeneric(u, type, begin, end) :
factoryInteger<uint8_t>(u, type, begin, end));
factoryInteger<uint8_t>(u, type, getEncapsulatedSpace(),
begin, end, callback));
case OPT_INT8_TYPE:
return (array_type_ ? factoryGeneric(u, type, begin, end) :
factoryInteger<int8_t>(u, type, begin, end));
factoryInteger<int8_t>(u, type, getEncapsulatedSpace(),
begin, end, callback));
case OPT_UINT16_TYPE:
return (array_type_ ?
factoryIntegerArray<uint16_t>(u, type, begin, end) :
factoryInteger<uint16_t>(u, type, begin, end));
factoryInteger<uint16_t>(u, type, getEncapsulatedSpace(),
begin, end, callback));
case OPT_INT16_TYPE:
return (array_type_ ?
factoryIntegerArray<uint16_t>(u, type, begin, end) :
factoryInteger<int16_t>(u, type, begin, end));
factoryInteger<int16_t>(u, type, getEncapsulatedSpace(),
begin, end, callback));
case OPT_UINT32_TYPE:
return (array_type_ ?
factoryIntegerArray<uint32_t>(u, type, begin, end) :
factoryInteger<uint32_t>(u, type, begin, end));
factoryInteger<uint32_t>(u, type, getEncapsulatedSpace(),
begin, end, callback));
case OPT_INT32_TYPE:
return (array_type_ ?
factoryIntegerArray<uint32_t>(u, type, begin, end) :
factoryInteger<int32_t>(u, type, begin, end));
factoryInteger<int32_t>(u, type, getEncapsulatedSpace(),
begin, end, callback));
case OPT_IPV4_ADDRESS_TYPE:
// If definition specifies that an option is an array
@ -211,8 +218,9 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
OptionPtr
OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
const OptionBuffer& buf) const {
return (optionFactory(u, type, buf.begin(), buf.end()));
const OptionBuffer& buf,
UnpackOptionsCallback callback) const {
return (optionFactory(u, type, buf.begin(), buf.end(), callback));
}
OptionPtr

View File

@ -315,7 +315,8 @@ public:
/// @throw InvalidOptionValue if data for the option is invalid.
OptionPtr optionFactory(Option::Universe u, uint16_t type,
OptionBufferConstIter begin,
OptionBufferConstIter end) const;
OptionBufferConstIter end,
UnpackOptionsCallback callback = NULL) const;
/// @brief Option factory.
///
@ -334,7 +335,8 @@ public:
/// @return instance of the DHCP option.
/// @throw InvalidOptionValue if data for the option is invalid.
OptionPtr optionFactory(Option::Universe u, uint16_t type,
const OptionBuffer& buf = OptionBuffer()) const;
const OptionBuffer& buf = OptionBuffer(),
UnpackOptionsCallback callback = NULL) const;
/// @brief Option factory.
///
@ -444,9 +446,14 @@ public:
/// @throw isc::OutOfRange if provided option buffer length is invalid.
template<typename T>
static OptionPtr factoryInteger(Option::Universe u, uint16_t type,
const std::string& encapsulated_space,
OptionBufferConstIter begin,
OptionBufferConstIter end) {
OptionPtr option(new OptionInt<T>(u, type, begin, end));
OptionBufferConstIter end,
UnpackOptionsCallback callback) {
OptionPtr option(new OptionInt<T>(u, type, 0));
option->setEncapsulatedSpace(encapsulated_space);
option->setCallback(callback);
option->unpack(begin, end);
return (option);
}

View File

@ -1,4 +1,4 @@
// 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
@ -52,6 +52,7 @@ public:
if (!OptionDataTypeTraits<T>::integer_type) {
isc_throw(dhcp::InvalidDataType, "non-integer type");
}
setEncapsulatedSpace(u == Option::V4 ? "dhcp4" : "dhcp6");
}
/// @brief Constructor.
@ -74,6 +75,7 @@ public:
if (!OptionDataTypeTraits<T>::integer_type) {
isc_throw(dhcp::InvalidDataType, "non-integer type");
}
setEncapsulatedSpace(u == Option::V4 ? "dhcp4" : "dhcp6");
unpack(begin, end);
}
@ -175,7 +177,7 @@ public:
// The data length is equal to size of T.
length += sizeof(T);;
// length of all suboptions
for (Option::OptionCollection::iterator it = options_.begin();
for (OptionCollection::iterator it = options_.begin();
it != options_.end();
++it) {
length += (*it).second->len();

View File

@ -1,4 +1,4 @@
// 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
@ -239,7 +239,7 @@ public:
uint16_t length = (getUniverse() == Option::V4) ? OPTION4_HDR_LEN : OPTION6_HDR_LEN;
length += values_.size() * sizeof(T);
// length of all suboptions
for (Option::OptionCollection::iterator it = options_.begin();
for (OptionCollection::iterator it = options_.begin();
it != options_.end();
++it) {
length += (*it).second->len();

View File

@ -93,7 +93,7 @@ Pkt4::len() {
size_t length = DHCPV4_PKT_HDR_LEN; // DHCPv4 header
// ... and sum of lengths of all options
for (Option::OptionCollection::const_iterator it = options_.begin();
for (OptionCollection::const_iterator it = options_.begin();
it != options_.end();
++it) {
length += (*it).second->len();
@ -209,11 +209,15 @@ Pkt4::unpack() {
}
size_t opts_len = bufferIn.getLength() - bufferIn.getPosition();
vector<uint8_t> optsBuffer;
vector<uint8_t> opts_buffer;
// First use of readVector.
bufferIn.readVector(optsBuffer, opts_len);
LibDHCP::unpackOptions4(optsBuffer, options_);
bufferIn.readVector(opts_buffer, opts_len);
if (callback_.empty()) {
LibDHCP::unpackOptions4(opts_buffer, options_);
} else {
callback_(opts_buffer, "dhcp4", options_, 0, 0);
}
// @todo check will need to be called separately, so hooks can be called
// after the packet is parsed, but before its content is verified
@ -270,7 +274,7 @@ Pkt4::toText() {
<< ":" << remote_port_ << ", msgtype=" << static_cast<int>(getType())
<< ", transid=0x" << hex << transid_ << dec << endl;
for (isc::dhcp::Option::OptionCollection::iterator opt=options_.begin();
for (isc::dhcp::OptionCollection::iterator opt=options_.begin();
opt != options_.end();
++opt) {
tmp << " " << opt->second->toText() << std::endl;
@ -428,7 +432,7 @@ Pkt4::addOption(boost::shared_ptr<Option> opt) {
boost::shared_ptr<isc::dhcp::Option>
Pkt4::getOption(uint8_t type) const {
Option::OptionCollection::const_iterator x = options_.find(type);
OptionCollection::const_iterator x = options_.find(type);
if (x != options_.end()) {
return (*x).second;
}
@ -437,7 +441,7 @@ Pkt4::getOption(uint8_t type) const {
bool
Pkt4::delOption(uint8_t type) {
isc::dhcp::Option::OptionCollection::iterator x = options_.find(type);
isc::dhcp::OptionCollection::iterator x = options_.find(type);
if (x != options_.end()) {
options_.erase(x);
return (true); // delete successful

View File

@ -16,6 +16,7 @@
#define PKT4_H
#include <asiolink/io_address.h>
#include <dhcp/option.h>
#include <util/buffer.h>
#include <dhcp/option.h>
#include <dhcp/hwaddr.h>
@ -482,6 +483,14 @@ public:
/// @return remote port
uint16_t getRemotePort() const { return (remote_port_); }
/// @brief Set callback function to be used to parse options.
///
/// @param callback An instance of the callback function or NULL to
/// uninstall callback.
void setCallback(UnpackOptionsCallback callback) {
callback_ = callback;
}
/// @brief Update packet timestamp.
///
/// Updates packet timestamp. This method is invoked
@ -632,11 +641,14 @@ protected:
/// behavior must be taken into consideration before making
/// changes to this member such as access scope restriction or
/// data format change etc.
isc::dhcp::Option::OptionCollection options_;
isc::dhcp::OptionCollection options_;
/// packet timestamp
boost::posix_time::ptime timestamp_;
/// A callback to be called to unpack options from the packet.
UnpackOptionsCallback callback_;
}; // Pkt4 class
typedef boost::shared_ptr<Pkt4> Pkt4Ptr;

View File

@ -14,6 +14,7 @@
#include <dhcp/dhcp6.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option.h>
#include <dhcp/pkt6.h>
#include <exceptions/exceptions.h>
@ -134,7 +135,7 @@ OptionPtr Pkt6::getRelayOption(uint16_t opt_type, uint8_t relay_level) {
<< " There is no info about " << relay_level + 1 << " relay.");
}
for (Option::OptionCollection::iterator it = relay_info_[relay_level].options_.begin();
for (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);
@ -148,7 +149,7 @@ 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();
for (OptionCollection::const_iterator opt = relay.options_.begin();
opt != relay.options_.end(); ++opt) {
len += (opt->second)->len();
}
@ -171,7 +172,7 @@ uint16_t Pkt6::calculateRelaySizes() {
uint16_t Pkt6::directLen() const {
uint16_t length = DHCPV6_PKT_HDR_LEN; // DHCPv6 header
for (Option::OptionCollection::const_iterator it = options_.begin();
for (OptionCollection::const_iterator it = options_.begin();
it != options_.end();
++it) {
length += (*it).second->len();
@ -226,7 +227,7 @@ Pkt6::packUDP() {
// 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 =
for (OptionCollection::const_iterator opt =
relay->options_.begin();
opt != relay->options_.end(); ++opt) {
(opt->second)->pack(bufferOut_);
@ -324,7 +325,13 @@ Pkt6::unpackMsg(OptionBuffer::const_iterator begin,
try {
OptionBuffer opt_buffer(begin, end);
LibDHCP::unpackOptions6(opt_buffer, options_);
// If custom option parsing function has been set, use this function
// to parse options. Otherwise, use standard function from libdhcp.
if (callback_.empty()) {
LibDHCP::unpackOptions6(opt_buffer, options_);
} else {
callback_(opt_buffer, "dhcp6", options_, 0, 0);
}
} catch (const Exception& e) {
// @todo: throw exception here once we turn this function to void.
return (false);
@ -361,8 +368,16 @@ Pkt6::unpackRelayMsg() {
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);
// If custom option parsing function has been set, use this function
// to parse options. Otherwise, use standard function from libdhcp.
if (callback_.empty()) {
LibDHCP::unpackOptions6(opt_buffer, relay.options_,
&relay_msg_offset, &relay_msg_len);
} else {
callback_(opt_buffer, "dhcp6", 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);
@ -438,7 +453,7 @@ Pkt6::toText() {
<< "]:" << remote_port_ << endl;
tmp << "msgtype=" << static_cast<int>(msg_type_) << ", transid=0x" <<
hex << transid_ << dec << endl;
for (isc::dhcp::Option::OptionCollection::iterator opt=options_.begin();
for (isc::dhcp::OptionCollection::iterator opt=options_.begin();
opt != options_.end();
++opt) {
tmp << opt->second->toText() << std::endl;
@ -448,18 +463,18 @@ Pkt6::toText() {
OptionPtr
Pkt6::getOption(uint16_t opt_type) {
isc::dhcp::Option::OptionCollection::const_iterator x = options_.find(opt_type);
isc::dhcp::OptionCollection::const_iterator x = options_.find(opt_type);
if (x!=options_.end()) {
return (*x).second;
}
return OptionPtr(); // NULL
}
isc::dhcp::Option::OptionCollection
isc::dhcp::OptionCollection
Pkt6::getOptions(uint16_t opt_type) {
isc::dhcp::Option::OptionCollection found;
isc::dhcp::OptionCollection found;
for (Option::OptionCollection::const_iterator x = options_.begin();
for (OptionCollection::const_iterator x = options_.begin();
x != options_.end(); ++x) {
if (x->first == opt_type) {
found.insert(make_pair(opt_type, x->second));
@ -475,7 +490,7 @@ Pkt6::addOption(const OptionPtr& opt) {
bool
Pkt6::delOption(uint16_t type) {
isc::dhcp::Option::OptionCollection::iterator x = options_.find(type);
isc::dhcp::OptionCollection::iterator x = options_.find(type);
if (x!=options_.end()) {
options_.erase(x);
return (true); // delete successful

View File

@ -88,7 +88,7 @@ public:
uint16_t relay_msg_len_;
/// options received from a specified relay, except relay-msg option
isc::dhcp::Option::OptionCollection options_;
isc::dhcp::OptionCollection options_;
};
/// Constructor, used in replying to a message
@ -242,7 +242,7 @@ public:
///
/// @param type option type we are looking for
/// @return instance of option collection with requested options
isc::dhcp::Option::OptionCollection getOptions(uint16_t type);
isc::dhcp::OptionCollection getOptions(uint16_t type);
/// Attempts to delete first suboption of requested type
///
@ -350,7 +350,7 @@ public:
/// behavior must be taken into consideration before making
/// changes to this member such as access scope restriction or
/// data format change etc.
isc::dhcp::Option::OptionCollection options_;
isc::dhcp::OptionCollection options_;
/// @brief Update packet timestamp.
///
@ -388,6 +388,14 @@ public:
/// be freed by the caller.
const char* getName() const;
/// @brief Set callback function to be used to parse options.
///
/// @param callback An instance of the callback function or NULL to
/// uninstall callback.
void setCallback(UnpackOptionsCallback callback) {
callback_ = callback;
}
/// @brief copies relay information from client's packet to server's response
///
/// This information is not simply copied over. Some parameter are
@ -534,6 +542,10 @@ protected:
/// packet timestamp
boost::posix_time::ptime timestamp_;
/// A callback to be called to unpack options from the packet.
UnpackOptionsCallback callback_;
}; // Pkt6 class
} // isc::dhcp namespace

View File

@ -254,7 +254,7 @@ TEST_F(LibDhcpTest, optionFactory) {
TEST_F(LibDhcpTest, packOptions6) {
OptionBuffer buf(512);
isc::dhcp::Option::OptionCollection opts; // list of options
isc::dhcp::OptionCollection opts; // list of options
// generate content for options
for (int i = 0; i < 64; i++) {
@ -286,7 +286,7 @@ TEST_F(LibDhcpTest, unpackOptions6) {
// Option is used as a simple option implementation
// More advanced uses are validated in tests dedicated for
// specific derived classes.
isc::dhcp::Option::OptionCollection options; // list of options
isc::dhcp::OptionCollection options; // list of options
OptionBuffer buf(512);
memcpy(&buf[0], v6packed, sizeof(v6packed));
@ -298,7 +298,7 @@ TEST_F(LibDhcpTest, unpackOptions6) {
EXPECT_EQ(options.size(), 5); // there should be 5 options
isc::dhcp::Option::OptionCollection::const_iterator x = options.find(1);
isc::dhcp::OptionCollection::const_iterator x = options.find(1);
ASSERT_FALSE(x == options.end()); // option 1 should exist
EXPECT_EQ(1, x->second->getType()); // this should be option 1
ASSERT_EQ(9, x->second->len()); // it should be of length 9
@ -399,7 +399,7 @@ TEST_F(LibDhcpTest, packOptions4) {
OptionPtr opt4(new Option(Option::V4,254, payload[3]));
OptionPtr opt5(new Option(Option::V4,128, payload[4]));
isc::dhcp::Option::OptionCollection opts; // list of options
isc::dhcp::OptionCollection opts; // list of options
opts.insert(make_pair(opt1->getType(), opt1));
opts.insert(make_pair(opt1->getType(), opt2));
opts.insert(make_pair(opt1->getType(), opt3));
@ -418,13 +418,13 @@ TEST_F(LibDhcpTest, packOptions4) {
TEST_F(LibDhcpTest, unpackOptions4) {
vector<uint8_t> v4packed(v4_opts, v4_opts + sizeof(v4_opts));
isc::dhcp::Option::OptionCollection options; // list of options
isc::dhcp::OptionCollection options; // list of options
ASSERT_NO_THROW(
LibDHCP::unpackOptions4(v4packed, options);
);
isc::dhcp::Option::OptionCollection::const_iterator x = options.find(12);
isc::dhcp::OptionCollection::const_iterator x = options.find(12);
ASSERT_FALSE(x == options.end()); // option 1 should exist
// Option 12 holds a string so let's cast it to an appropriate type.
OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x->second);

View File

@ -951,8 +951,8 @@ TEST_F(OptionDefinitionTest, integerInvalidType) {
// see if it rejects it.
OptionBuffer buf(1);
EXPECT_THROW(
OptionDefinition::factoryInteger<bool>(Option::V6, D6O_PREFERENCE,
buf.begin(), buf.end()),
OptionDefinition::factoryInteger<bool>(Option::V6, D6O_PREFERENCE, "dhcp6",
buf.begin(), buf.end(), NULL),
isc::dhcp::InvalidDataType
);
}

View File

@ -15,10 +15,12 @@
#include <config.h>
#include <dhcp/dhcp6.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option.h>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
@ -35,6 +37,66 @@ using namespace isc::util;
using boost::scoped_ptr;
namespace {
/// @brief A class which contains a custom callback function to unpack options.
///
/// This is a class used by the tests which verify that the custom callback
/// functions can be installed to unpack options from a message. When the
/// callback function is called, the executed_ member is set to true to allow
/// verification that the callback was really called. Internally, this class
/// uses libdhcp++ to unpack options so the options parsing algorithm remains
/// unchanged after installation of the callback.
class CustomUnpackCallback {
public:
/// @brief Constructor
///
/// Marks that callback hasn't been called.
CustomUnpackCallback()
: executed_(false) {
}
/// @brief A callback
///
/// Contains custom implementation of the callback.
///
/// @param buf a A buffer holding options in on-wire format.
/// @param [out] options A reference to the collection where parsed options
/// will be stored.
/// @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 An offset to the first byte after last parsed option.
size_t execute(const OptionBuffer& buf,
const std::string&,
isc::dhcp::OptionCollection& options,
size_t* relay_msg_offset,
size_t* relay_msg_len) {
// Set the executed_ member to true to allow verification that the
// callback has been actually called.
executed_ = true;
// Use default implementation of the unpack algorithm to parse options.
return (LibDHCP::unpackOptions6(buf, options, relay_msg_offset,
relay_msg_len));
}
/// A flag which indicates if callback function has been called.
bool executed_;
};
/// @brief A class which derives from option and exposes protected members.
class NakedOption : public Option {
public:
/// @brief Constructor
///
/// Sets the universe and option type to arbitrary test values.
NakedOption() : Option(Option::V6, 258) {
}
using Option::unpackOptions;
};
class OptionTest : public ::testing::Test {
public:
OptionTest(): buf_(255), outBuf_(255) {
@ -505,4 +567,68 @@ TEST_F(OptionTest, equal) {
EXPECT_TRUE(opt2->equal(opt5));
}
// This test verifies that the name of the option space being encapsulated by
// the particular option can be set.
TEST_F(OptionTest, setEncapsulatedSpace) {
Option optv6(Option::V6, 258);
EXPECT_TRUE(optv6.getEncapsulatedSpace().empty());
optv6.setEncapsulatedSpace("dhcp6");
EXPECT_EQ("dhcp6", optv6.getEncapsulatedSpace());
Option optv4(Option::V4, 125);
EXPECT_TRUE(optv4.getEncapsulatedSpace().empty());
optv4.setEncapsulatedSpace("dhcp4");
EXPECT_EQ("dhcp4", optv4.getEncapsulatedSpace());
}
// This test verifies that it is possible to specify custom implementation of
// the option parsing algorithm by installing a callback function.
TEST_F(OptionTest, unpackCallback) {
// Create a buffer which holds two sub options.
const char opt_data[] = {
0x00, 0x01, // sub option code = 1
0x00, 0x02, // sub option length = 1
0x00, 0x01, // sub option data
0x00, 0x02, // sub option code = 2
0x00, 0x02, // sub option length
0x00, 0x01 // sub option data
};
OptionBuffer opt_buf(opt_data, opt_data + sizeof(opt_data));
// Make sure that the flag which indicates if the callback function has
// been called is not set. Otherwise, our test doesn't make sense.
CustomUnpackCallback cb;
ASSERT_FALSE(cb.executed_);
// Create an option and install a callback.
NakedOption option;
option.setCallback(boost::bind(&CustomUnpackCallback::execute, &cb,
_1, _2, _3, _4, _5));
// Parse options. It should result in a call to our callback function.
// This function uses LibDHCP to parse options so they should be parsed
// correctly.
ASSERT_NO_THROW(option.unpackOptions(opt_buf));
EXPECT_TRUE(option.getOption(1));
EXPECT_TRUE(option.getOption(2));
EXPECT_FALSE(option.getOption(3));
// The callback should have been registered.
EXPECT_TRUE(cb.executed_);
// Reset the flag because now we are going to uninstall the callback and
// verify that it was NOT called.
cb.executed_ = false;
// Uninstall the callback.
option.setCallback(NULL);
ASSERT_NO_THROW(option.unpackOptions(opt_buf));
// Options should still get unpacked...
EXPECT_TRUE(option.getOption(1));
EXPECT_TRUE(option.getOption(2));
EXPECT_FALSE(option.getOption(3));
// ... but not via callback.
EXPECT_FALSE(cb.executed_);
}
}

View File

@ -16,6 +16,7 @@
#include <asiolink/io_address.h>
#include <dhcp/dhcp4.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option_string.h>
#include <dhcp/pkt4.h>
#include <exceptions/exceptions.h>
@ -42,40 +43,58 @@ using boost::scoped_ptr;
namespace {
TEST(Pkt4Test, constructor) {
/// @brief A class which contains a custom callback function to unpack options.
///
/// This is a class used by the tests which verify that the custom callback
/// functions can be installed to unpack options from a message. When the
/// callback function is called, the executed_ member is set to true to allow
/// verification that the callback was really called. Internally, this class
/// uses libdhcp++ to unpack options so the options parsing algorithm remains
/// unchanged after installation of the callback.
class CustomUnpackCallback {
public:
ASSERT_EQ(236U, static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) );
scoped_ptr<Pkt4> pkt;
// Just some dummy payload.
uint8_t testData[250];
for (int i = 0; i < 250; i++) {
testData[i] = i;
/// @brief Constructor
///
/// Marks that callback hasn't been called.
CustomUnpackCallback()
: executed_(false) {
}
// Positive case1. Normal received packet.
EXPECT_NO_THROW(pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN)));
/// @brief A callback
///
/// Contains custom implementation of the callback.
///
/// @param buf a A buffer holding options in on-wire format.
/// @param [out] options A reference to the collection where parsed options
/// will be stored.
/// @return An offset to the first byte after last parsed option.
size_t execute(const OptionBuffer& buf,
const std::string&,
isc::dhcp::OptionCollection& options) {
// Set the executed_ member to true to allow verification that the
// callback has been actually called.
executed_ = true;
// Use default implementation of the unpack algorithm to parse options.
return (LibDHCP::unpackOptions4(buf, options));
}
EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len());
/// A flag which indicates if callback function has been called.
bool executed_;
};
EXPECT_NO_THROW(pkt.reset());
// Positive case2. Normal outgoing packet.
EXPECT_NO_THROW(pkt.reset(new Pkt4(DHCPDISCOVER, 0xffffffff)));
// DHCPv4 packet must be at least 236 bytes long, with Message Type
// Option taking extra 3 bytes it is 239
EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
EXPECT_EQ(DHCPDISCOVER, pkt->getType());
EXPECT_EQ(0xffffffff, pkt->getTransid());
EXPECT_NO_THROW(pkt.reset());
// Negative case. Should drop truncated messages.
EXPECT_THROW(
pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN - 1)),
OutOfRange
);
}
/// V4 Options being used for pack/unpack testing.
/// For test simplicity, all selected options have
/// variable length data so as there are no restrictions
/// on a length of their data.
static uint8_t v4_opts[] = {
12, 3, 0, 1, 2, // Hostname
14, 3, 10, 11, 12, // Merit Dump File
53, 1, 2, // Message Type (required to not throw exception during unpack)
60, 3, 20, 21, 22, // Class Id
128, 3, 30, 31, 32, // Vendor specific
254, 3, 40, 41, 42, // Reserved
};
// Sample data
const uint8_t dummyOp = BOOTREQUEST;
@ -110,82 +129,179 @@ const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur "
BOOST_STATIC_ASSERT(sizeof(dummyFile) == Pkt4::MAX_FILE_LEN + 1);
BOOST_STATIC_ASSERT(sizeof(dummySname) == Pkt4::MAX_SNAME_LEN + 1);
/// @brief Generates test packet.
///
/// Allocates and generates test packet, with all fixed fields set to non-zero
/// values. Content is not always reasonable.
///
/// See generateTestPacket2() function that returns exactly the same packet in
/// on-wire format.
///
/// @return pointer to allocated Pkt4 object.
boost::shared_ptr<Pkt4>
generateTestPacket1() {
boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDISCOVER, dummyTransid));
class Pkt4Test : public ::testing::Test {
public:
Pkt4Test() {
}
vector<uint8_t> vectorMacAddr(dummyMacAddr, dummyMacAddr
+sizeof(dummyMacAddr));
/// @brief Generates test packet.
///
/// Allocates and generates test packet, with all fixed fields set to non-zero
/// values. Content is not always reasonable.
///
/// See generateTestPacket2() function that returns exactly the same packet in
/// on-wire format.
///
/// @return pointer to allocated Pkt4 object.
Pkt4Ptr generateTestPacket1() {
// hwType = 6(ETHERNET), hlen = 6(MAC address len)
pkt->setHWAddr(dummyHtype, dummyHlen, vectorMacAddr);
pkt->setHops(dummyHops); // 13 relays. Wow!
// Transaction-id is already set.
pkt->setSecs(dummySecs);
pkt->setFlags(dummyFlags); // all flags set
pkt->setCiaddr(dummyCiaddr);
pkt->setYiaddr(dummyYiaddr);
pkt->setSiaddr(dummySiaddr);
pkt->setGiaddr(dummyGiaddr);
// Chaddr already set with setHWAddr().
pkt->setSname(dummySname, 64);
pkt->setFile(dummyFile, 128);
boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDISCOVER, dummyTransid));
return (pkt);
vector<uint8_t> vectorMacAddr(dummyMacAddr, dummyMacAddr
+ sizeof(dummyMacAddr));
// hwType = 6(ETHERNET), hlen = 6(MAC address len)
pkt->setHWAddr(dummyHtype, dummyHlen, vectorMacAddr);
pkt->setHops(dummyHops); // 13 relays. Wow!
// Transaction-id is already set.
pkt->setSecs(dummySecs);
pkt->setFlags(dummyFlags); // all flags set
pkt->setCiaddr(dummyCiaddr);
pkt->setYiaddr(dummyYiaddr);
pkt->setSiaddr(dummySiaddr);
pkt->setGiaddr(dummyGiaddr);
// Chaddr already set with setHWAddr().
pkt->setSname(dummySname, 64);
pkt->setFile(dummyFile, 128);
return (pkt);
}
/// @brief Generates test packet.
///
/// Allocates and generates on-wire buffer that represents test packet, with all
/// fixed fields set to non-zero values. Content is not always reasonable.
///
/// See generateTestPacket1() function that returns exactly the same packet as
/// Pkt4 object.
///
/// @return pointer to allocated Pkt4 object
// Returns a vector containing a DHCPv4 packet header.
vector<uint8_t> generateTestPacket2() {
// That is only part of the header. It contains all "short" fields,
// larger fields are constructed separately.
uint8_t hdr[] = {
1, 6, 6, 13, // op, htype, hlen, hops,
0x12, 0x34, 0x56, 0x78, // transaction-id
0, 42, 0x80, 0x00, // 42 secs, BROADCAST flags
192, 0, 2, 1, // ciaddr
1, 2, 3, 4, // yiaddr
192, 0, 2, 255, // siaddr
255, 255, 255, 255, // giaddr
};
// Initialize the vector with the header fields defined above.
vector<uint8_t> buf(hdr, hdr + sizeof(hdr));
// Append the large header fields.
copy(dummyChaddr, dummyChaddr + Pkt4::MAX_CHADDR_LEN, back_inserter(buf));
copy(dummySname, dummySname + Pkt4::MAX_SNAME_LEN, back_inserter(buf));
copy(dummyFile, dummyFile + Pkt4::MAX_FILE_LEN, back_inserter(buf));
// Should now have all the header, so check. The "static_cast" is used
// to get round an odd bug whereby the linker appears not to find the
// definition of DHCPV4_PKT_HDR_LEN if it appears within an EXPECT_EQ().
EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), buf.size());
return (buf);
}
/// @brief Verify that the options are correct after parsing.
///
/// @param pkt A packet holding parsed options.
void verifyParsedOptions(const Pkt4Ptr& pkt) {
EXPECT_TRUE(pkt->getOption(12));
EXPECT_TRUE(pkt->getOption(60));
EXPECT_TRUE(pkt->getOption(14));
EXPECT_TRUE(pkt->getOption(128));
EXPECT_TRUE(pkt->getOption(254));
boost::shared_ptr<Option> x = pkt->getOption(12);
ASSERT_TRUE(x); // option 1 should exist
// Option 12 is represented by the OptionString class so let's do
// the appropriate conversion.
OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x);
ASSERT_TRUE(option12);
EXPECT_EQ(12, option12->getType()); // this should be option 12
ASSERT_EQ(3, option12->getValue().length()); // it should be of length 3
EXPECT_EQ(5, option12->len()); // total option length 5
EXPECT_EQ(0, memcmp(&option12->getValue()[0], v4_opts + 2, 3)); // data len=3
x = pkt->getOption(14);
ASSERT_TRUE(x); // option 14 should exist
// Option 14 is represented by the OptionString class so let's do
// the appropriate conversion.
OptionStringPtr option14 = boost::static_pointer_cast<OptionString>(x);
ASSERT_TRUE(option14);
EXPECT_EQ(14, option14->getType()); // this should be option 14
ASSERT_EQ(3, option14->getValue().length()); // it should be of length 3
EXPECT_EQ(5, option14->len()); // total option length 5
EXPECT_EQ(0, memcmp(&option14->getValue()[0], v4_opts + 7, 3)); // data len=3
x = pkt->getOption(60);
ASSERT_TRUE(x); // option 60 should exist
EXPECT_EQ(60, x->getType()); // this should be option 60
ASSERT_EQ(3, x->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->len()); // total option length 5
EXPECT_EQ(0, memcmp(&x->getData()[0], v4_opts + 15, 3)); // data len=3
x = pkt->getOption(128);
ASSERT_TRUE(x); // option 3 should exist
EXPECT_EQ(128, x->getType()); // this should be option 254
ASSERT_EQ(3, x->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->len()); // total option length 5
EXPECT_EQ(0, memcmp(&x->getData()[0], v4_opts + 20, 3)); // data len=3
x = pkt->getOption(254);
ASSERT_TRUE(x); // option 3 should exist
EXPECT_EQ(254, x->getType()); // this should be option 254
ASSERT_EQ(3, x->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->len()); // total option length 5
EXPECT_EQ(0, memcmp(&x->getData()[0], v4_opts + 25, 3)); // data len=3
}
};
TEST_F(Pkt4Test, constructor) {
ASSERT_EQ(236U, static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) );
scoped_ptr<Pkt4> pkt;
// Just some dummy payload.
uint8_t testData[250];
for (int i = 0; i < 250; i++) {
testData[i] = i;
}
// Positive case1. Normal received packet.
EXPECT_NO_THROW(pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN)));
EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len());
EXPECT_NO_THROW(pkt.reset());
// Positive case2. Normal outgoing packet.
EXPECT_NO_THROW(pkt.reset(new Pkt4(DHCPDISCOVER, 0xffffffff)));
// DHCPv4 packet must be at least 236 bytes long, with Message Type
// Option taking extra 3 bytes it is 239
EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
EXPECT_EQ(DHCPDISCOVER, pkt->getType());
EXPECT_EQ(0xffffffff, pkt->getTransid());
EXPECT_NO_THROW(pkt.reset());
// Negative case. Should drop truncated messages.
EXPECT_THROW(
pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN - 1)),
OutOfRange
);
}
/// @brief Generates test packet.
///
/// Allocates and generates on-wire buffer that represents test packet, with all
/// fixed fields set to non-zero values. Content is not always reasonable.
///
/// See generateTestPacket1() function that returns exactly the same packet as
/// Pkt4 object.
///
/// @return pointer to allocated Pkt4 object
// Returns a vector containing a DHCPv4 packet header.
vector<uint8_t>
generateTestPacket2() {
// That is only part of the header. It contains all "short" fields,
// larger fields are constructed separately.
uint8_t hdr[] = {
1, 6, 6, 13, // op, htype, hlen, hops,
0x12, 0x34, 0x56, 0x78, // transaction-id
0, 42, 0x80, 0x00, // 42 secs, BROADCAST flags
192, 0, 2, 1, // ciaddr
1, 2, 3, 4, // yiaddr
192, 0, 2, 255, // siaddr
255, 255, 255, 255, // giaddr
};
// Initialize the vector with the header fields defined above.
vector<uint8_t> buf(hdr, hdr + sizeof(hdr));
// Append the large header fields.
copy(dummyChaddr, dummyChaddr + Pkt4::MAX_CHADDR_LEN, back_inserter(buf));
copy(dummySname, dummySname + Pkt4::MAX_SNAME_LEN, back_inserter(buf));
copy(dummyFile, dummyFile + Pkt4::MAX_FILE_LEN, back_inserter(buf));
// Should now have all the header, so check. The "static_cast" is used
// to get round an odd bug whereby the linker appears not to find the
// definition of DHCPV4_PKT_HDR_LEN if it appears within an EXPECT_EQ().
EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), buf.size());
return (buf);
}
TEST(Pkt4Test, fixedFields) {
TEST_F(Pkt4Test, fixedFields) {
boost::shared_ptr<Pkt4> pkt = generateTestPacket1();
@ -215,7 +331,7 @@ TEST(Pkt4Test, fixedFields) {
EXPECT_EQ(DHCPDISCOVER, pkt->getType());
}
TEST(Pkt4Test, fixedFieldsPack) {
TEST_F(Pkt4Test, fixedFieldsPack) {
boost::shared_ptr<Pkt4> pkt = generateTestPacket1();
vector<uint8_t> expectedFormat = generateTestPacket2();
@ -235,7 +351,7 @@ TEST(Pkt4Test, fixedFieldsPack) {
}
/// TODO Uncomment when ticket #1226 is implemented
TEST(Pkt4Test, fixedFieldsUnpack) {
TEST_F(Pkt4Test, fixedFieldsUnpack) {
vector<uint8_t> expectedFormat = generateTestPacket2();
expectedFormat.push_back(0x63); // magic cookie
@ -282,7 +398,7 @@ TEST(Pkt4Test, fixedFieldsUnpack) {
}
// This test is for hardware addresses (htype, hlen and chaddr fields)
TEST(Pkt4Test, hwAddr) {
TEST_F(Pkt4Test, hwAddr) {
vector<uint8_t> mac;
uint8_t expectedChaddr[Pkt4::MAX_CHADDR_LEN];
@ -329,7 +445,7 @@ TEST(Pkt4Test, hwAddr) {
/// longer than 16 bytes should be stored in client-identifier option
}
TEST(Pkt4Test, msgTypes) {
TEST_F(Pkt4Test, msgTypes) {
struct msgType {
uint8_t dhcp;
@ -366,7 +482,7 @@ TEST(Pkt4Test, msgTypes) {
}
// This test verifies handling of sname field
TEST(Pkt4Test, sname) {
TEST_F(Pkt4Test, sname) {
uint8_t sname[Pkt4::MAX_SNAME_LEN];
@ -404,7 +520,7 @@ TEST(Pkt4Test, sname) {
EXPECT_THROW(pkt4.setSname(NULL, 0), InvalidParameter);
}
TEST(Pkt4Test, file) {
TEST_F(Pkt4Test, file) {
uint8_t file[Pkt4::MAX_FILE_LEN];
@ -442,20 +558,7 @@ TEST(Pkt4Test, file) {
EXPECT_THROW(pkt4.setFile(NULL, 0), InvalidParameter);
}
/// V4 Options being used for pack/unpack testing.
/// For test simplicity, all selected options have
/// variable length data so as there are no restrictions
/// on a length of their data.
static uint8_t v4Opts[] = {
12, 3, 0, 1, 2, // Hostname
14, 3, 10, 11, 12, // Merit Dump File
53, 1, 2, // Message Type (required to not throw exception during unpack)
60, 3, 20, 21, 22, // Class Id
128, 3, 30, 31, 32, // Vendor specific
254, 3, 40, 41, 42, // Reserved
};
TEST(Pkt4Test, options) {
TEST_F(Pkt4Test, options) {
scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 0));
vector<uint8_t> payload[5];
@ -496,10 +599,10 @@ TEST(Pkt4Test, options) {
);
const OutputBuffer& buf = pkt->getBuffer();
// Check that all options are stored, they should take sizeof(v4Opts),
// Check that all options are stored, they should take sizeof(v4_opts),
// DHCP magic cookie (4 bytes), and OPTION_END added (just one byte)
ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) +
sizeof(DHCP_OPTIONS_COOKIE) + sizeof(v4Opts) + 1,
sizeof(DHCP_OPTIONS_COOKIE) + sizeof(v4_opts) + 1,
buf.getLength());
// That that this extra data actually contain our options
@ -508,8 +611,8 @@ TEST(Pkt4Test, options) {
// Rewind to end of fixed part.
ptr += Pkt4::DHCPV4_PKT_HDR_LEN + sizeof(DHCP_OPTIONS_COOKIE);
EXPECT_EQ(0, memcmp(ptr, v4Opts, sizeof(v4Opts)));
EXPECT_EQ(DHO_END, static_cast<uint8_t>(*(ptr + sizeof(v4Opts))));
EXPECT_EQ(0, memcmp(ptr, v4_opts, sizeof(v4_opts)));
EXPECT_EQ(DHO_END, static_cast<uint8_t>(*(ptr + sizeof(v4_opts))));
// delOption() checks
EXPECT_TRUE(pkt->getOption(12)); // Sanity check: option 12 is still there
@ -520,7 +623,8 @@ TEST(Pkt4Test, options) {
EXPECT_NO_THROW(pkt.reset());
}
TEST(Pkt4Test, unpackOptions) {
// This test verifies that the options are unpacked from the packet correctly.
TEST_F(Pkt4Test, unpackOptions) {
vector<uint8_t> expectedFormat = generateTestPacket2();
@ -529,8 +633,8 @@ TEST(Pkt4Test, unpackOptions) {
expectedFormat.push_back(0x53);
expectedFormat.push_back(0x63);
for (int i = 0; i < sizeof(v4Opts); i++) {
expectedFormat.push_back(v4Opts[i]);
for (int i = 0; i < sizeof(v4_opts); i++) {
expectedFormat.push_back(v4_opts[i]);
}
// now expectedFormat contains fixed format and 5 options
@ -542,59 +646,53 @@ TEST(Pkt4Test, unpackOptions) {
pkt->unpack()
);
EXPECT_TRUE(pkt->getOption(12));
EXPECT_TRUE(pkt->getOption(60));
EXPECT_TRUE(pkt->getOption(14));
EXPECT_TRUE(pkt->getOption(128));
EXPECT_TRUE(pkt->getOption(254));
verifyParsedOptions(pkt);
}
boost::shared_ptr<Option> x = pkt->getOption(12);
ASSERT_TRUE(x); // option 1 should exist
// Option 12 is represented by the OptionString class so let's do
// the appropriate conversion.
OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x);
ASSERT_TRUE(option12);
EXPECT_EQ(12, option12->getType()); // this should be option 12
ASSERT_EQ(3, option12->getValue().length()); // it should be of length 3
EXPECT_EQ(5, option12->len()); // total option length 5
EXPECT_EQ(0, memcmp(&option12->getValue()[0], v4Opts + 2, 3)); // data len=3
// This test verifies that it is possible to specify custom implementation of
// the option parsing algorithm by installing a callback function.
TEST_F(Pkt4Test, unpackOptionsWithCallback) {
vector<uint8_t> expectedFormat = generateTestPacket2();
x = pkt->getOption(14);
ASSERT_TRUE(x); // option 14 should exist
// Option 14 is represented by the OptionString class so let's do
// the appropriate conversion.
OptionStringPtr option14 = boost::static_pointer_cast<OptionString>(x);
ASSERT_TRUE(option14);
EXPECT_EQ(14, option14->getType()); // this should be option 14
ASSERT_EQ(3, option14->getValue().length()); // it should be of length 3
EXPECT_EQ(5, option14->len()); // total option length 5
EXPECT_EQ(0, memcmp(&option14->getValue()[0], v4Opts + 7, 3)); // data len=3
expectedFormat.push_back(0x63);
expectedFormat.push_back(0x82);
expectedFormat.push_back(0x53);
expectedFormat.push_back(0x63);
x = pkt->getOption(60);
ASSERT_TRUE(x); // option 60 should exist
EXPECT_EQ(60, x->getType()); // this should be option 60
ASSERT_EQ(3, x->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->len()); // total option length 5
EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 15, 3)); // data len=3
for (int i = 0; i < sizeof(v4_opts); i++) {
expectedFormat.push_back(v4_opts[i]);
}
x = pkt->getOption(128);
ASSERT_TRUE(x); // option 3 should exist
EXPECT_EQ(128, x->getType()); // this should be option 254
ASSERT_EQ(3, x->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->len()); // total option length 5
EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 20, 3)); // data len=3
// now expectedFormat contains fixed format and 5 options
boost::shared_ptr<Pkt4> pkt(new Pkt4(&expectedFormat[0],
expectedFormat.size()));
CustomUnpackCallback cb;
pkt->setCallback(boost::bind(&CustomUnpackCallback::execute, &cb,
_1, _2, _3));
ASSERT_FALSE(cb.executed_);
EXPECT_NO_THROW(pkt->unpack());
EXPECT_TRUE(cb.executed_);
verifyParsedOptions(pkt);
// Reset the indicator to perform another check: uninstall the callback.
cb.executed_ = false;
// By setting the callback to NULL we effectively uninstall the callback.
pkt->setCallback(NULL);
// Do another unpack.
EXPECT_NO_THROW(pkt->unpack());
// Callback should not be executed.
EXPECT_FALSE(cb.executed_);
x = pkt->getOption(254);
ASSERT_TRUE(x); // option 3 should exist
EXPECT_EQ(254, x->getType()); // this should be option 254
ASSERT_EQ(3, x->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->len()); // total option length 5
EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 25, 3)); // data len=3
}
// This test verifies methods that are used for manipulating meta fields
// i.e. fields that are not part of DHCPv4 (e.g. interface name).
TEST(Pkt4Test, metaFields) {
TEST_F(Pkt4Test, metaFields) {
scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
pkt->setIface("loooopback");
@ -608,7 +706,7 @@ TEST(Pkt4Test, metaFields) {
EXPECT_EQ("4.3.2.1", pkt->getLocalAddr().toText());
}
TEST(Pkt4Test, Timestamp) {
TEST_F(Pkt4Test, Timestamp) {
scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
// Just after construction timestamp is invalid
@ -634,7 +732,7 @@ TEST(Pkt4Test, Timestamp) {
EXPECT_TRUE(ts_period.length().total_microseconds() >= 0);
}
TEST(Pkt4Test, hwaddr) {
TEST_F(Pkt4Test, hwaddr) {
scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
const uint8_t hw_type = 123; // hardware type
@ -655,7 +753,7 @@ TEST(Pkt4Test, hwaddr) {
// This test verifies that the packet remte and local HW address can
// be set and returned.
TEST(Pkt4Test, hwaddrSrcRemote) {
TEST_F(Pkt4Test, hwaddrSrcRemote) {
scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
const uint8_t src_hw[] = { 1, 2, 3, 4, 5, 6 };
const uint8_t dst_hw[] = { 7, 8, 9, 10, 11, 12 };

View File

@ -24,6 +24,7 @@
#include <dhcp/pkt6.h>
#include <util/range_utilities.h>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/scoped_ptr.hpp>
#include <util/encode/hex.h>
@ -41,7 +42,54 @@ using namespace isc::dhcp;
using boost::scoped_ptr;
namespace {
// empty class for now, but may be extended once Addr6 becomes bigger
/// @brief A class which contains a custom callback function to unpack options.
///
/// This is a class used by the tests which verify that the custom callback
/// functions can be installed to unpack options from a message. When the
/// callback function is called, the executed_ member is set to true to allow
/// verification that the callback was really called. Internally, this class
/// uses libdhcp++ to unpack options so the options parsing algorithm remains
/// unchanged after installation of the callback.
class CustomUnpackCallback {
public:
/// @brief Constructor
///
/// Marks that callback hasn't been called.
CustomUnpackCallback()
: executed_(false) {
}
/// @brief A callback
///
/// Contains custom implementation of the callback.
///
/// @param buf a A buffer holding options in on-wire format.
/// @param [out] options A reference to the collection where parsed options
/// will be stored.
/// @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 An offset to the first byte after last parsed option.
size_t execute(const OptionBuffer& buf,
const std::string&,
isc::dhcp::OptionCollection& options,
size_t* relay_msg_offset,
size_t* relay_msg_len) {
// Set the executed_ member to true to allow verification that the
// callback has been actually called.
executed_ = true;
// Use default implementation of the unpack algorithm to parse options.
return (LibDHCP::unpackOptions6(buf, options, relay_msg_offset,
relay_msg_len));
}
/// A flag which indicates if callback function has been called.
bool executed_;
};
class Pkt6Test : public ::testing::Test {
public:
Pkt6Test() {
@ -58,6 +106,38 @@ public:
util::fillRandom(data.begin(), data.end());
return OptionPtr(new Option(Option::V6, code, data));
}
/// @brief Create a wire representation of the test packet and clone it.
Pkt6Ptr packAndClone() {
Pkt6Ptr parent(new Pkt6(DHCPV6_SOLICIT, 0x020304));
OptionPtr opt1(new Option(Option::V6, 1));
OptionPtr opt2(new Option(Option::V6, 2));
OptionPtr opt3(new Option(Option::V6, 100));
// Let's not use zero-length option type 3 as it is IA_NA
parent->addOption(opt1);
parent->addOption(opt2);
parent->addOption(opt3);
EXPECT_EQ(DHCPV6_SOLICIT, parent->getType());
// Calculated length should be 16
EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN,
parent->len());
EXPECT_NO_THROW(parent->pack());
EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN,
parent->len());
// Create second packet,based on assembled data from the first one
Pkt6Ptr clone(new Pkt6(static_cast<const uint8_t*>
(parent->getBuffer().getData()),
parent->getBuffer().getLength()));
return (clone);
}
};
TEST_F(Pkt6Test, constructor) {
@ -204,38 +284,14 @@ TEST_F(Pkt6Test, unpack_solicit1) {
}
TEST_F(Pkt6Test, packUnpack) {
scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_SOLICIT, 0x020304));
OptionPtr opt1(new Option(Option::V6, 1));
OptionPtr opt2(new Option(Option::V6, 2));
OptionPtr opt3(new Option(Option::V6, 100));
// Let's not use zero-length option type 3 as it is IA_NA
parent->addOption(opt1);
parent->addOption(opt2);
parent->addOption(opt3);
EXPECT_EQ(DHCPV6_SOLICIT, parent->getType());
// Calculated length should be 16
EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN,
parent->len());
EXPECT_NO_THROW(parent->pack());
EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN,
parent->len());
// Create second packet,based on assembled data from the first one
scoped_ptr<Pkt6> clone(new Pkt6(
static_cast<const uint8_t*>(parent->getBuffer().getData()),
parent->getBuffer().getLength()));
// Create an on-wire representation of the test packet and clone it.
Pkt6Ptr clone = packAndClone();
// Now recreate options list
EXPECT_TRUE( clone->unpack() );
EXPECT_TRUE(clone->unpack());
// transid, message-type should be the same as before
EXPECT_EQ(parent->getTransid(), parent->getTransid());
EXPECT_EQ(0x020304, clone->getTransid());
EXPECT_EQ(DHCPV6_SOLICIT, clone->getType());
EXPECT_TRUE(clone->getOption(1));
@ -244,6 +300,48 @@ TEST_F(Pkt6Test, packUnpack) {
EXPECT_FALSE(clone->getOption(4));
}
// This test verifies that it is possible to specify custom implementation of
// the option parsing algorithm by installing a callback function.
TEST_F(Pkt6Test, packUnpackWithCallback) {
// Create an on-wire representation of the test packet and clone it.
Pkt6Ptr clone = packAndClone();
// Install the custom callback function. We expect that this function
// will be called to parse options in the packet instead of
// LibDHCP::unpackOptions6.
CustomUnpackCallback cb;
clone->setCallback(boost::bind(&CustomUnpackCallback::execute, &cb,
_1, _2, _3, _4, _5));
// Make sure that the flag which indicates if the callback function has
// been called is not set. Otherwise, our test doesn't make sense.
ASSERT_FALSE(cb.executed_);
// Now recreate options list
EXPECT_TRUE(clone->unpack());
// An object which holds a callback should now have a flag set which
// indicates that callback has been called.
EXPECT_TRUE(cb.executed_);
// transid, message-type should be the same as before
EXPECT_EQ(0x020304, clone->getTransid());
EXPECT_EQ(DHCPV6_SOLICIT, clone->getType());
EXPECT_TRUE(clone->getOption(1));
EXPECT_TRUE(clone->getOption(2));
EXPECT_TRUE(clone->getOption(100));
EXPECT_FALSE(clone->getOption(4));
// Reset the indicator to perform another check: uninstall the callback.
cb.executed_ = false;
// By setting the callback to NULL we effectively uninstall the callback.
clone->setCallback(NULL);
// Do another unpack.
EXPECT_TRUE(clone->unpack());
// Callback should not be executed.
EXPECT_FALSE(cb.executed_);
}
// This test verifies that options can be added (addOption()), retrieved
// (getOption(), getOptions()) and deleted (delOption()).
TEST_F(Pkt6Test, addGetDelOptions) {
@ -266,12 +364,12 @@ TEST_F(Pkt6Test, addGetDelOptions) {
// Now there are 2 options of type 2
parent->addOption(opt3);
Option::OptionCollection options = parent->getOptions(2);
OptionCollection options = parent->getOptions(2);
EXPECT_EQ(2, options.size()); // there should be 2 instances
// Both options must be of type 2 and there must not be
// any other type returned
for (Option::OptionCollection::const_iterator x= options.begin();
for (OptionCollection::const_iterator x= options.begin();
x != options.end(); ++x) {
EXPECT_EQ(2, x->second->getType());
}