diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc index 7d13631bb3..c6332a9b73 100644 --- a/src/bin/dhcp4/dhcp4_srv.cc +++ b/src/bin/dhcp4/dhcp4_srv.cc @@ -35,6 +35,7 @@ #include #include +#include #include #include @@ -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(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(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 diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h index f53f2a7508..32f1c6ea1e 100644 --- a/src/bin/dhcp4/dhcp4_srv.h +++ b/src/bin/dhcp4/dhcp4_srv.h @@ -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 /// diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index be130052e5..801c965bc3 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -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); +} + + }; }; diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index f8f0b2f6c7..ded1fe7455 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -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 diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc index 024b589b4c..5812711734 100644 --- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc +++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -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 > option_foobar = + boost::dynamic_pointer_cast >(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 > option_foo = + boost::dynamic_pointer_cast >(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 > option_bar = + boost::dynamic_pointer_cast >(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. diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.h b/src/bin/dhcp6/tests/dhcp6_test_utils.h index 6035d269bd..1deaca27ff 100644 --- a/src/bin/dhcp6/tests/dhcp6_test_utils.h +++ b/src/bin/dhcp6/tests/dhcp6_test_utils.h @@ -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 diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc index 697c33efa6..351fe8ca83 100644 --- a/src/lib/dhcp/libdhcp++.cc +++ b/src/lib/dhcp/libdhcp++.cc @@ -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); } diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h index 9d8bcab7ad..71cf3f4969 100644 --- a/src/lib/dhcp/libdhcp++.h +++ b/src/lib/dhcp/libdhcp++.h @@ -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); diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc index cd6e313d4b..46889e0530 100644 --- a/src/lib/dhcp/option.cc +++ b/src/lib/dhcp/option.cc @@ -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 diff --git a/src/lib/dhcp/option.h b/src/lib/dhcp/option.h index 9abb4b38c9..85fa165f1f 100644 --- a/src/lib/dhcp/option.h +++ b/src/lib/dhcp/option.h @@ -17,6 +17,7 @@ #include +#include #include #include @@ -44,6 +45,14 @@ typedef boost::shared_ptr OptionBufferPtr; class Option; typedef boost::shared_ptr