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

[master] Merge branch 'trac2417'

parents 560a7191 d5deb4ca
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
#include <cc/data.h> #include <cc/data.h>
#include <config/ccsession.h> #include <config/ccsession.h>
#include <log/logger_support.h> #include <log/logger_support.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/triplet.h> #include <dhcp/triplet.h>
#include <dhcp/pool.h> #include <dhcp/pool.h>
#include <dhcp/subnet.h> #include <dhcp/subnet.h>
...@@ -616,7 +617,11 @@ private: ...@@ -616,7 +617,11 @@ private:
<< " spaces"); << " spaces");
} }
// Get option data from the configuration database ('data' field).
// Option data is specified by the user as case insensitive string
// of hexadecimal digits for each option.
std::string option_data = getStringParam("data"); std::string option_data = getStringParam("data");
// Transform string of hexadecimal digits into binary format.
std::vector<uint8_t> binary; std::vector<uint8_t> binary;
try { try {
util::encode::decodeHex(option_data, binary); util::encode::decodeHex(option_data, binary);
...@@ -624,16 +629,49 @@ private: ...@@ -624,16 +629,49 @@ private:
isc_throw(Dhcp6ConfigError, "Parser error: option data is not a valid" isc_throw(Dhcp6ConfigError, "Parser error: option data is not a valid"
<< " string of hexadecimal digits: " << option_data); << " string of hexadecimal digits: " << option_data);
} }
// Get all existing DHCPv6 option definitions. The one that matches
// Create the actual option. // our option will be picked and used to create it.
// @todo Currently we simply create dhcp::Option instance here but we will OptionDefContainer option_defs = LibDHCP::getOptionDefs(Option::V6);
// need to use dedicated factory functions once the option definitions are // Get search index #1. It allows searching for options definitions
// created for all options. // using option type value.
OptionPtr option(new Option(Option::V6, static_cast<uint16_t>(option_code), const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
binary)); // Get all option definitions matching option code we want to create.
const OptionDefContainerTypeRange& range = idx.equal_range(option_code);
// If option is created succesfully, add it to the storage. size_t num_defs = std::distance(range.first, range.second);
options_->push_back(option); OptionPtr option;
// Currently we do not allow duplicated definitions and if there are
// any duplicates we issue internal server error.
if (num_defs > 1) {
isc_throw(Dhcp6ConfigError, "Internal error: currently it is not"
<< " supported to initialize multiple option definitions"
<< " for the same option code. This will be supported once"
<< " there option spaces are implemented.");
} else if (num_defs == 0) {
// @todo We have a limited set of option definitions intiialized at the moment.
// In the future we want to initialize option definitions for all options.
// Consequently error will be issued if option definition does not exist
// for a particular option code. For now it is ok to create generic option
// if definition does not exist.
OptionPtr option(new Option(Option::V6, static_cast<uint16_t>(option_code),
binary));
// If option is created succesfully, add it to the storage.
options_->push_back(option);
} else {
// We have exactly one option definition for the particular option code.
// use it to create option instance.
const OptionDefinitionPtr& def = *(range.first);
// getFactory should never return NULL pointer.
Option::Factory* factory = def->getFactory();
assert(factory != NULL);
try {
OptionPtr option = factory(Option::V6, option_code, binary);
options_->push_back(option);
} catch (const isc::Exception& ex) {
isc_throw(Dhcp6ConfigError, "Parser error: option data does not match"
<< " option definition (code " << option_code << "): "
<< ex.what());
}
}
} }
/// @brief Get a parameter from the strings storage. /// @brief Get a parameter from the strings storage.
......
...@@ -110,6 +110,16 @@ This is a debug message issued during the IPv6 DHCP server startup. ...@@ -110,6 +110,16 @@ This is a debug message issued during the IPv6 DHCP server startup.
It lists some information about the parameters with which the server It lists some information about the parameters with which the server
is running. is running.
% DHCP6_NO_SUBNET_DEF_OPT failed to find subnet for address %1 when adding default options
This warning message indicates that when attempting to add default options to a response,
the server found that it was not configured to support the subnet from which the DHCPv6
request was received. The packet has been ignored.
% DHCP6_NO_SUBNET_REQ_OPT failed to find subnet for address %1 when adding requested options
This warning message indicates that when attempting to add requested options to a response,
the server found that it was not configured to support the subnet from which the DHCPv6
request was received. The packet has been ignored.
% DHCP6_CONFIG_LOAD_FAIL failed to load configuration: %1 % DHCP6_CONFIG_LOAD_FAIL failed to load configuration: %1
This critical error message indicates that the initial DHCPv6 This critical error message indicates that the initial DHCPv6
configuration has failed. The server will start, but nothing will be configuration has failed. The server will start, but nothing will be
......
...@@ -20,14 +20,20 @@ ...@@ -20,14 +20,20 @@
#include <dhcp6/dhcp6_srv.h> #include <dhcp6/dhcp6_srv.h>
#include <dhcp/dhcp6.h> #include <dhcp/dhcp6.h>
#include <dhcp/iface_mgr.h> #include <dhcp/iface_mgr.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option6_addrlst.h> #include <dhcp/option6_addrlst.h>
#include <dhcp/option6_iaaddr.h> #include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_ia.h> #include <dhcp/option6_ia.h>
#include <dhcp/option6_int_array.h>
#include <dhcp/pkt6.h> #include <dhcp/pkt6.h>
#include <dhcp/subnet.h>
#include <dhcp/cfgmgr.h>
#include <exceptions/exceptions.h> #include <exceptions/exceptions.h>
#include <util/io_utilities.h> #include <util/io_utilities.h>
#include <util/range_utilities.h> #include <util/range_utilities.h>
#include <boost/foreach.hpp>
using namespace isc; using namespace isc;
using namespace isc::asiolink; using namespace isc::asiolink;
using namespace isc::dhcp; using namespace isc::dhcp;
...@@ -51,10 +57,17 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port) { ...@@ -51,10 +57,17 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port); LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
// First call to instance() will create IfaceMgr (it's a singleton) // Initialize objects required for DHCP server operation.
// it may throw something if things go wrong
try { try {
// Initialize standard DHCPv6 option definitions. This function
// may throw bad_alloc if system goes out of memory during the
// creation if option definitions. It may also throw isc::Unexpected
// if definitions are wrong. This would mean error in implementation.
initStdOptionDefs();
// Call IfaceMgr::instance() will create instance of Interface
// Manager (it's a singleton). It may throw if things go wrong.
if (IfaceMgr::instance().countIfaces() == 0) { if (IfaceMgr::instance().countIfaces() == 0) {
LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES); LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
shutdown_ = true; shutdown_ = true;
...@@ -281,23 +294,63 @@ void Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) { ...@@ -281,23 +294,63 @@ void Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// TODO: Should throw if there is no client-id (except anonymous INF-REQUEST) // TODO: Should throw if there is no client-id (except anonymous INF-REQUEST)
} }
void Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr& /*question*/, Pkt6Ptr& answer) { void Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// TODO: question is currently unused, but we need it at least to know
// message type we are answering
// add server-id // add server-id
answer->addOption(getServerID()); answer->addOption(getServerID());
}
// Get the subnet object. It holds options to be sent to the client
// that belongs to the particular subnet.
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
// Warn if subnet is not supported and quit.
if (!subnet) {
LOG_WARN(dhcp6_logger, DHCP6_NO_SUBNET_DEF_OPT)
.arg(question->getRemoteAddr().toText());
return;
}
// Add DNS_SERVERS option. It should have been configured.
const Subnet::OptionContainer& options = subnet->getOptions();
const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
const Subnet::OptionContainerTypeRange range =
idx.equal_range(D6O_NAME_SERVERS);
// In theory we may have multiple options with the same
// option code. They are not differentiated right now
// until support for option spaces is implemented.
// Until that's the case, simply add the first found option.
if (std::distance(range.first, range.second) > 0) {
answer->addOption(range.first->option);
}
}
void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& /*question*/, Pkt6Ptr& answer) { void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// TODO: question is currently unused, but we need to extract ORO from it // Get the subnet for a particular address.
// and act on its content. Now we just send DNS-SERVERS option. Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
if (!subnet) {
LOG_WARN(dhcp6_logger, DHCP6_NO_SUBNET_REQ_OPT)
.arg(question->getRemoteAddr().toText());
return;
}
// add dns-servers option // Client requests some options using ORO option. Try to
boost::shared_ptr<Option> dnsservers(new Option6AddrLst(D6O_NAME_SERVERS, // get this option from client's message.
IOAddress(HARDCODED_DNS_SERVER))); boost::shared_ptr<Option6IntArray<uint16_t> > option_oro =
answer->addOption(dnsservers); boost::dynamic_pointer_cast<Option6IntArray<uint16_t> >(question->getOption(D6O_ORO));
// Option ORO not found. Don't do anything then.
if (!option_oro) {
return;
}
// Get the list of options that client requested.
const std::vector<uint16_t>& requested_opts = option_oro->getValues();
// Get the list of options configured for a subnet.
const Subnet::OptionContainer& options = subnet->getOptions();
const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
// Try to match requested options with those configured for a subnet.
// If match is found, append configured option to the answer message.
BOOST_FOREACH(uint16_t opt, requested_opts) {
const Subnet::OptionContainerTypeRange& range = idx.equal_range(opt);
BOOST_FOREACH(Subnet::OptionDescriptor desc, range) {
answer->addOption(desc.option);
}
}
} }
void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) { void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
...@@ -428,3 +481,8 @@ Dhcpv6Srv::serverReceivedPacketName(uint8_t type) { ...@@ -428,3 +481,8 @@ Dhcpv6Srv::serverReceivedPacketName(uint8_t type) {
} }
return (UNKNOWN); return (UNKNOWN);
} }
void
Dhcpv6Srv::initStdOptionDefs() {
LibDHCP::initStdOptionDefs(Option::V6);
}
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include <dhcp/dhcp6.h> #include <dhcp/dhcp6.h>
#include <dhcp/pkt6.h> #include <dhcp/pkt6.h>
#include <dhcp/option.h> #include <dhcp/option.h>
#include <dhcp/option_definition.h>
#include <iostream> #include <iostream>
namespace isc { namespace isc {
...@@ -170,8 +171,6 @@ protected: ...@@ -170,8 +171,6 @@ protected:
/// @brief Appends requested options to server's answer. /// @brief Appends requested options to server's answer.
/// ///
/// Appends options requested by client to the server's answer. /// Appends options requested by client to the server's answer.
/// TODO: This method is currently a stub. It just appends DNS-SERVERS
/// option.
/// ///
/// @param question client's message /// @param question client's message
/// @param answer server's message (options will be added here) /// @param answer server's message (options will be added here)
...@@ -199,6 +198,18 @@ protected: ...@@ -199,6 +198,18 @@ protected:
/// interfaces for new DUID generation are detected. /// interfaces for new DUID generation are detected.
void setServerID(); void setServerID();
/// @brief Initializes option definitions for standard options.
///
/// Each standard option's format is described by the
/// dhcp::OptionDefinition object. This function creates such objects
/// for each standard DHCPv6 option.
///
/// @todo list thrown exceptions.
/// @todo extend this function to cover all standard options. Currently
/// it is limited to critical options only.
void initStdOptionDefs();
private:
/// server DUID (to be sent in server-identifier option) /// server DUID (to be sent in server-identifier option)
boost::shared_ptr<isc::dhcp::Option> serverid_; boost::shared_ptr<isc::dhcp::Option> serverid_;
......
...@@ -17,14 +17,18 @@ ...@@ -17,14 +17,18 @@
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <boost/foreach.hpp>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <dhcp6/dhcp6_srv.h> #include <dhcp6/dhcp6_srv.h>
#include <dhcp6/config_parser.h> #include <dhcp6/config_parser.h>
#include <config/ccsession.h> #include <config/ccsession.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/subnet.h> #include <dhcp/subnet.h>
#include <dhcp/cfgmgr.h> #include <dhcp/cfgmgr.h>
#include <dhcp/option6_ia.h>
using namespace std; using namespace std;
using namespace isc; using namespace isc;
...@@ -43,6 +47,10 @@ public: ...@@ -43,6 +47,10 @@ public:
// deal with sockets here, just check if configuration handling // deal with sockets here, just check if configuration handling
// is sane. // is sane.
srv_ = new Dhcpv6Srv(0); srv_ = new Dhcpv6Srv(0);
// Create instances of option definitions and put them into storage.
// This is normally initialized by the server when calling run()
// run() function.
LibDHCP::initStdOptionDefs(Option::V6);
} }
~Dhcp6ParserTest() { ~Dhcp6ParserTest() {
...@@ -60,6 +68,24 @@ public: ...@@ -60,6 +68,24 @@ public:
/// param value. /// param value.
std::string createConfigWithOption(const std::string& param_value, std::string createConfigWithOption(const std::string& param_value,
const std::string& parameter) { const std::string& parameter) {
std::map<std::string, std::string> params;
if (parameter == "name") {
params["name"] = param_value;
params["code"] = "80";
params["data"] = "AB CDEF0105";
} else if (parameter == "code") {
params["name"] = "option_foo";
params["code"] = param_value;
params["data"] = "AB CDEF0105";
} else if (parameter == "data") {
params["name"] = "option_foo";
params["code"] = "80";
params["data"] = param_value;
}
return (createConfigWithOption(params));
}
std::string createConfigWithOption(const std::map<std::string, std::string>& params) {
std::ostringstream stream; std::ostringstream stream;
stream << "{ \"interface\": [ \"all\" ]," stream << "{ \"interface\": [ \"all\" ],"
"\"preferred-lifetime\": 3000," "\"preferred-lifetime\": 3000,"
...@@ -69,21 +95,21 @@ public: ...@@ -69,21 +95,21 @@ public:
" \"pool\": [ \"2001:db8:1::/80\" ]," " \"pool\": [ \"2001:db8:1::/80\" ],"
" \"subnet\": \"2001:db8:1::/64\", " " \"subnet\": \"2001:db8:1::/64\", "
" \"option-data\": [ {"; " \"option-data\": [ {";
if (parameter == "name") { bool first = true;
stream << typedef std::pair<std::string, std::string> ParamPair;
" \"name\": \"" << param_value << "\"," BOOST_FOREACH(ParamPair param, params) {
" \"code\": 80," if (!first) {
" \"data\": \"AB CDEF0105\""; stream << ", ";
} else if (parameter == "code") { } else {
stream << first = false;
" \"name\": \"option_foo\"," }
" \"code\": " << param_value << "," if (param.first == "name") {
" \"data\": \"AB CDEF0105\""; stream << "\"name\": \"" << param.second << "\"";
} else if (parameter == "data") { } else if (param.first == "code") {
stream << stream << "\"code\": " << param.second << "";
" \"name\": \"option_foo\"," } else if (param.first == "data") {
" \"code\": 80," stream << "\"data\": \"" << param.second << "\"";
" \"data\": \"" << param_value << "\""; }
} }
stream << stream <<
" } ]" " } ]"
...@@ -658,4 +684,58 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) { ...@@ -658,4 +684,58 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
testOption(*range.first, 80, foo_expected, sizeof(foo_expected)); testOption(*range.first, 80, foo_expected, sizeof(foo_expected));
} }
// Verify that specific option object is returned for standard
// option which has dedicated option class derived from Option.
TEST_F(Dhcp6ParserTest, stdOptionData) {
ConstElementPtr x;
std::map<std::string, std::string> params;
params["name"] = "OPTION_IA_NA";
// Option code 3 means OPTION_IA_NA.
params["code"] = "3";
params["data"] = "ABCDEF01 02030405 06070809";
std::string config = createConfigWithOption(params);
ElementPtr json = Element::fromJSON(config);
EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
ASSERT_EQ(0, rcode_);
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet);
const Subnet::OptionContainer& options = subnet->getOptions();
ASSERT_EQ(1, options.size());
// Get the search index. Index #1 is to search using option code.
const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
// code so we get the range.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
idx.equal_range(D6O_IA_NA);
// Expect single option with the code equal to IA_NA option code.
ASSERT_EQ(1, std::distance(range.first, range.second));
// The actual pointer to the option is held in the option field
// in the structure returned.
OptionPtr option = range.first->option;
ASSERT_TRUE(option);
// Option object returned for here is expected to be Option6IA
// which is derived from Option. This class is dedicated to
// represent standard option IA_NA.
boost::shared_ptr<Option6IA> optionIA =
boost::dynamic_pointer_cast<Option6IA>(option);
// If cast is unsuccessful than option returned was of a
// differnt type than Option6IA. This is wrong.
ASSERT_TRUE(optionIA);
// If cast was successful we may use accessors exposed by
// Option6IA to validate that the content of this option
// has been set correctly.
EXPECT_EQ(0xABCDEF01, optionIA->getIAID());
EXPECT_EQ(0x02030405, optionIA->getT1());
EXPECT_EQ(0x06070809, optionIA->getT2());
}
}; };
...@@ -20,9 +20,13 @@ ...@@ -20,9 +20,13 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/dhcp6_srv.h>
#include <dhcp/dhcp6.h> #include <dhcp/dhcp6.h>
#include <dhcp/option6_ia.h> #include <dhcp/option6_ia.h>
#include <dhcp6/dhcp6_srv.h> #include <dhcp/option6_addrlst.h>
#include <dhcp/option6_int_array.h>
#include <config/ccsession.h>
#include <util/buffer.h> #include <util/buffer.h>
#include <util/range_utilities.h> #include <util/range_utilities.h>
#include <boost/scoped_ptr.hpp> #include <boost/scoped_ptr.hpp>
...@@ -31,6 +35,9 @@ using namespace std; ...@@ -31,6 +35,9 @@ using namespace std;
using namespace isc; using namespace isc;
using namespace isc::dhcp; using namespace isc::dhcp;
using namespace isc::util; using namespace isc::util;
using namespace isc::data;
using namespace isc::config;
using namespace isc::asiolink;
// namespace has to be named, because friends are defined in Dhcpv6Srv class // namespace has to be named, because friends are defined in Dhcpv6Srv class
// Maybe it should be isc::test? // Maybe it should be isc::test?
...@@ -53,11 +60,14 @@ public: ...@@ -53,11 +60,14 @@ public:
class Dhcpv6SrvTest : public ::testing::Test { class Dhcpv6SrvTest : public ::testing::Test {
public: public:
// these are empty for now, but let's keep them around Dhcpv6SrvTest()
Dhcpv6SrvTest() { : rcode_(-1) {
} }
~Dhcpv6SrvTest() { ~Dhcpv6SrvTest() {
}; };
int rcode_;
ConstElementPtr comment_;
}; };
TEST_F(Dhcpv6SrvTest, basic) { TEST_F(Dhcpv6SrvTest, basic) {
...@@ -66,7 +76,7 @@ TEST_F(Dhcpv6SrvTest, basic) { ...@@ -66,7 +76,7 @@ TEST_F(Dhcpv6SrvTest, basic) {
// fe80::1234 link-local address on eth0 interface. Obviously // fe80::1234 link-local address on eth0 interface. Obviously
// an attempt to bind this socket will fail. // an attempt to bind this socket will fail.
Dhcpv6Srv* srv = NULL; Dhcpv6Srv* srv = NULL;
ASSERT_NO_THROW( { ASSERT_NO_THROW({
// open an unpriviledged port // open an unpriviledged port
srv = new Dhcpv6Srv(DHCP6_SERVER_PORT + 10000); srv = new Dhcpv6Srv(DHCP6_SERVER_PORT + 10000);
}); });
...@@ -78,7 +88,7 @@ TEST_F(Dhcpv6SrvTest, DUID) { ...@@ -78,7 +88,7 @@ TEST_F(Dhcpv6SrvTest, DUID) {
// tests that DUID is generated properly // tests that DUID is generated properly
boost::scoped_ptr<Dhcpv6Srv> srv; boost::scoped_ptr<Dhcpv6Srv> srv;
ASSERT_NO_THROW( { ASSERT_NO_THROW({
srv.reset(new Dhcpv6Srv(DHCP6_SERVER_PORT + 10000)); srv.reset(new Dhcpv6Srv(DHCP6_SERVER_PORT + 10000));
}); });
...@@ -150,9 +160,39 @@ TEST_F(Dhcpv6SrvTest, DUID) { ...@@ -150,9 +160,39 @@ TEST_F(Dhcpv6SrvTest, DUID) {
} }
} }
TEST_F(Dhcpv6SrvTest, Solicit_basic) { TEST_F(Dhcpv6SrvTest, solicitBasic) {
ConstElementPtr x;
string config = "{ \"interface\": [ \"all\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1234::/80\" ],"
" \"subnet\": \"2001:db8:1234::/64\", "
" \"option-data\": [ {"
" \"name\": \"OPTION_DNS_SERVERS\","
" \"code\": 23,"
" \"data\": \"2001 0DB8 1234 FFFF 0000 0000 0000 0001"
"2001 0DB8 1234 FFFF 0000 0000 0000 0002\""
" },"
" {"
" \"name\": \"OPTION_FOO\","
" \"code\": 1000,"
" \"data\": \"1234\""
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::