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

[master] Merge branch 'trac4498'

parents 3f6b5678 aa0bfc57
......@@ -654,15 +654,6 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
isc::stats::StatsMgr::instance().addValue("pkt4-received",
static_cast<int64_t>(1));
// 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
......@@ -2355,102 +2346,6 @@ Dhcpv4Srv::sanityCheck(const Pkt4Ptr& query, RequirementLevel serverid) {
}
}
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 == "dhcp4") {
// Get the list of standard option definitions.
option_defs = LibDHCP::getOptionDefs(Option::V4);
} else if (!option_space.empty()) {
OptionDefContainerPtr option_defs_ptr = CfgMgr::instance()
.getCurrentCfg()->getCfgOptionDef()->getAll(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()) {
// We peeked at the option header of the next option, but discovered
// that it would end up beyond buffer end, so the option is
// truncated. Hence we can't parse it. Therefore we revert
// back by two bytes (as if we never parsed them).
return (offset - 2);
// 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);
}
void Dhcpv4Srv::classifyByVendor(const Pkt4Ptr& pkt, std::string& classes) {
// Built-in vendor class processing
boost::shared_ptr<OptionString> vendor_class =
......
......@@ -729,18 +729,6 @@ protected:
/// simulates transmission of a packet. For that purpose it is protected.
virtual void sendPacket(const Pkt4Ptr& 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.
/// @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 Assigns incoming packet to zero or more classes.
///
/// @note This is done in two phases: first the content of the
......
// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -15,6 +15,7 @@
#include <dhcp/tests/pkt_captures.h>
#include <dhcp/dhcp4.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option.h>
#include <dhcp/option_int.h>
#include <dhcp/option4_addrlst.h>
......@@ -1142,187 +1143,6 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsDocsis) {
/// See ticket #3057
// 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')
// @todo Add more thorough unit tests for unpackOptions.
TEST_F(Dhcpv4SrvTest, unpackSubOptions) {
// 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.
CfgOptionDefPtr cfg_option_def =
CfgMgr::instance().getStagingCfg()->getCfgOptionDef();
ASSERT_NO_THROW(cfg_option_def->add(opt_def, "space-foobar"));
ASSERT_NO_THROW(cfg_option_def->add(opt_def2, "space-foo"));
ASSERT_NO_THROW(cfg_option_def->add(opt_def3, "space-bar"));
CfgMgr::instance().commit();
// Create the buffer holding the structure of options.
const uint8_t raw_data[] = {
// First option starts here.
0x01, // option code = 1
0x0B, // option length = 11
0x00, 0x01, 0x02, 0x03, // This option carries uint32 value
// Sub option starts here.
0x01, // option code = 1
0x05, // option length = 5
0x01, 0x02, // this option carries uint16 value
// Last option starts here.
0x01, // option code = 1
0x01, // option length = 1
0x00 // This option carries a single uint8
// value and has no sub options.
};
size_t raw_data_len = sizeof(raw_data) / sizeof(uint8_t);
OptionBuffer buf(raw_data, raw_data + raw_data_len);
// Parse options.
NakedDhcpv4Srv srv(0);
OptionCollection options;
ASSERT_NO_THROW(srv.unpackOptions(buf, "space-foobar", options));
// 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());
}
// Check parsing of an empty option
TEST_F(Dhcpv4SrvTest, unpackEmptyOption) {
// Create option definition for the option code 1 without fields.
OptionDefinitionPtr opt_def(new OptionDefinition("option-empty", 1,
"empty", false));
// Add it to the Configuration Manager.
CfgOptionDefPtr cfg_option_def =
CfgMgr::instance().getStagingCfg()->getCfgOptionDef();
ASSERT_NO_THROW(cfg_option_def->add(opt_def, "space-empty"));
CfgMgr::instance().commit();
// Create the buffer holding the structure of the empty option.
const uint8_t raw_data[] = {
0x01, // option code = 1
0x00 // option length = 0
};
size_t raw_data_len = sizeof(raw_data) / sizeof(uint8_t);
OptionBuffer buf(raw_data, raw_data + raw_data_len);
// Parse options.
NakedDhcpv4Srv srv(0);
OptionCollection options;
ASSERT_NO_THROW(srv.unpackOptions(buf, "space-empty", options));
// There should be one option.
ASSERT_EQ(1, options.size());
OptionPtr option_empty = options.begin()->second;
ASSERT_TRUE(option_empty);
EXPECT_EQ(1, option_empty->getType());
EXPECT_EQ(2, option_empty->len());
}
// Check parsing of an empty VSI sub option
TEST_F(Dhcpv4SrvTest, unpackVSIOption) {
// Create the buffer holding the structure of the Vendor-Specific Info
const uint8_t raw_data[] = {
43, // option code = DHO_VENDOR_ENCAPSULATED_OPTIONS
2, // option length
0xdc, // suboption code
0 // suboption length
};
size_t raw_data_len = sizeof(raw_data) / sizeof(uint8_t);
OptionBuffer buf(raw_data, raw_data + raw_data_len);
// Parse options.
NakedDhcpv4Srv srv(0);
OptionCollection options;
ASSERT_NO_THROW(srv.unpackOptions(buf, "dhcp4", options));
// There should be one option: the VSI
ASSERT_EQ(1, options.size());
OptionPtr vsi = options.begin()->second;
ASSERT_TRUE(vsi);
EXPECT_EQ(DHO_VENDOR_ENCAPSULATED_OPTIONS, vsi->getType());
OptionCollection suboptions = vsi->getOptions();
// There should be one suboption
ASSERT_EQ(1, suboptions.size());
OptionPtr eso = suboptions.begin()->second;
ASSERT_TRUE(eso);
EXPECT_EQ(0xdc, eso->getType());
EXPECT_EQ(2, eso->len());
}
// Check parsing of an empty VIVSI sub option
TEST_F(Dhcpv4SrvTest, unpackVIVSIOption) {
// Create the buffer holding the structure of the Vendor-Identifying
// Vendor-Specific Info
const uint8_t raw_data[] = {
125, // option code = DHO_VIVSO_SUBOPTIONS
7, // option length
0, 0, 9, 0xbf, // ISC enterprise number (2495)
2, // option data length
0xdc, // suboption code
0 // suboption length
};
size_t raw_data_len = sizeof(raw_data) / sizeof(uint8_t);
OptionBuffer buf(raw_data, raw_data + raw_data_len);
// Parse options.
NakedDhcpv4Srv srv(0);
OptionCollection options;
ASSERT_NO_THROW(srv.unpackOptions(buf, "dhcp4", options));
// There should be one option: the VIVSI
ASSERT_EQ(1, options.size());
OptionPtr vivsi = options.begin()->second;
ASSERT_TRUE(vivsi);
EXPECT_EQ(DHO_VIVSO_SUBOPTIONS, vivsi->getType());
// Cast to OptionVendor
OptionVendorPtr vsi = boost::dynamic_pointer_cast<OptionVendor>(vivsi);
ASSERT_TRUE(vsi);
EXPECT_EQ(2495, vsi->getVendorId());
OptionCollection suboptions = vsi->getOptions();
// There should be one suboption
ASSERT_EQ(1, suboptions.size());
OptionPtr eso = suboptions.begin()->second;
ASSERT_TRUE(eso);
EXPECT_EQ(0xdc, eso->getType());
EXPECT_EQ(2, eso->len());
}
// Checks whether the server uses default (0.0.0.0) siaddr value, unless
// explicitly specified
TEST_F(Dhcpv4SrvTest, siaddrDefault) {
......
......@@ -11,6 +11,7 @@
#include <cc/command_interpreter.h>
#include <dhcp4/json_config_parser.h>
#include <dhcp4/tests/dhcp4_test_utils.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option4_addrlst.h>
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
......@@ -67,6 +68,8 @@ Dhcpv4SrvTest::Dhcpv4SrvTest()
CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet_);
CfgMgr::instance().commit();
LibDHCP::clearRuntimeOptionDefs();
// Let's wipe all existing statistics.
isc::stats::StatsMgr::instance().removeAll();
}
......@@ -77,6 +80,8 @@ Dhcpv4SrvTest::~Dhcpv4SrvTest() {
CfgMgr::instance().clear();
CfgMgr::instance().echoClientId(true);
LibDHCP::clearRuntimeOptionDefs();
// Let's wipe all existing statistics.
isc::stats::StatsMgr::instance().removeAll();
}
......
......@@ -194,7 +194,6 @@ public:
using Dhcpv4Srv::acceptServerId;
using Dhcpv4Srv::sanityCheck;
using Dhcpv4Srv::srvidToString;
using Dhcpv4Srv::unpackOptions;
using Dhcpv4Srv::classifyPacket;
using Dhcpv4Srv::accept;
using Dhcpv4Srv::acceptMessageType;
......
......@@ -469,15 +469,6 @@ void Dhcpv6Srv::run_one() {
void
Dhcpv6Srv::processPacket(Pkt6Ptr& query, Pkt6Ptr& rsp) {
// 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
......@@ -1604,11 +1595,9 @@ Dhcpv6Srv::extendIA_NA(const Pkt6Ptr& query, const Pkt6Ptr& answer,
Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<Option6IAAddr>(it->second);
if (!iaaddr) {
// That's weird. Option code was ok, but the object type was not.
// As we use Dhcpv6Srv::unpackOptions() that is guaranteed to use
// Option6IAAddr for D6O_IAADDR, this should never happen. The only
// case would be with badly mis-implemented hook libraries that
// insert invalid option objects. There's no way to protect against
// this.
// This should never happen. The only case would be with badly
// mis-implemented hook libraries that insert invalid option objects.
// There's no way to protect against this.
continue;
}
ctx.hints_.push_back(make_pair(iaaddr->getAddress(), 128));
......@@ -1758,11 +1747,9 @@ Dhcpv6Srv::extendIA_PD(const Pkt6Ptr& query,
Option6IAPrefixPtr prf = boost::dynamic_pointer_cast<Option6IAPrefix>(it->second);
if (!prf) {
// That's weird. Option code was ok, but the object type was not.
// As we use Dhcpv6Srv::unpackOptions() that is guaranteed to use
// Option6IAPrefix for D6O_IAPREFIX, this should never happen. The only
// case would be with badly mis-implemented hook libraries that
// insert invalid option objects. There's no way to protect against
// this.
// This should never happen. The only case would be with badly
// mis-implemented hook libraries that insert invalid option objects.
// There's no way to protect against this.
continue;
}
......@@ -2779,108 +2766,6 @@ Dhcpv6Srv::processInfRequest(const Pkt6Ptr& inf_request) {
return (reply);
}
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 standard option definitions.
option_defs = LibDHCP::getOptionDefs(Option::V6);
} else if (!option_space.empty()) {
OptionDefContainerPtr option_defs_ptr =
CfgMgr::instance().getCurrentCfg()->getCfgOptionDef()->
getAll(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) {
// At this point, from the while condition, we know that there
// are at least 4 bytes available following offset in the
// buffer.
uint16_t opt_type = isc::util::readUint16(&buf[offset], 2);
offset += 2;
uint16_t opt_len = isc::util::readUint16(&buf[offset], 2);
offset += 2;
if (offset + opt_len > length) {
// @todo: consider throwing exception here.
// We peeked at the option header of the next option, but discovered
// that it would end up beyond buffer end, so the option is
// truncated. Hence we can't parse it. Therefore we revert
// by by those four bytes (as if we never parsed them).
return (offset - 4);
}
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);
}
void Dhcpv6Srv::classifyByVendor(const Pkt6Ptr& pkt, std::string& classes) {
OptionVendorClassPtr vclass = boost::dynamic_pointer_cast<
OptionVendorClass>(pkt->getOption(D6O_VENDOR_CLASS));
......
......@@ -607,24 +607,6 @@ 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);
/// @brief Assigns incoming packet to zero or more classes.
///
/// @note This is done in two phases: first the content of the
......
// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -1721,79 +1721,6 @@ TEST_F(Dhcpv6SrvTest, vendorOptionsDocsisDefinitions) {
ASSERT_EQ(0, rcode_);
}
// 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.
CfgOptionDefPtr cfg_option_def =
CfgMgr::instance().getStagingCfg()->getCfgOptionDef();
ASSERT_NO_THROW(cfg_option_def->add(opt_def, "space-foobar"));
ASSERT_NO_THROW(cfg_option_def->add(opt_def2, "space-foo"));
ASSERT_NO_THROW(cfg_option_def->add(opt_def3, "space-bar"));
CfgMgr::instance().commit();
// 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.