Commit d06b0d68 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

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

parent 23c94464
......@@ -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)
};
......