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:
parent
23c944640d
commit
d06b0d68f9
@ -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
|
||||
|
@ -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
|
||||
///
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
};
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
@ -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_);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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 };
|
||||
|
@ -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());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user