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

[master] Merge branch 'trac2417'

parents 560a7191 d5deb4ca
......@@ -26,6 +26,7 @@
#include <cc/data.h>
#include <config/ccsession.h>
#include <log/logger_support.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/triplet.h>
#include <dhcp/pool.h>
#include <dhcp/subnet.h>
......@@ -616,7 +617,11 @@ private:
<< " 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");
// Transform string of hexadecimal digits into binary format.
std::vector<uint8_t> binary;
try {
util::encode::decodeHex(option_data, binary);
......@@ -624,16 +629,49 @@ private:
isc_throw(Dhcp6ConfigError, "Parser error: option data is not a valid"
<< " string of hexadecimal digits: " << option_data);
}
// Create the actual option.
// @todo Currently we simply create dhcp::Option instance here but we will
// need to use dedicated factory functions once the option definitions are
// created for all options.
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);
// Get all existing DHCPv6 option definitions. The one that matches
// our option will be picked and used to create it.
OptionDefContainer option_defs = LibDHCP::getOptionDefs(Option::V6);
// Get search index #1. It allows searching for options definitions
// using option type value.
const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
// Get all option definitions matching option code we want to create.
const OptionDefContainerTypeRange& range = idx.equal_range(option_code);
size_t num_defs = std::distance(range.first, range.second);
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.
......
......@@ -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
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
This critical error message indicates that the initial DHCPv6
configuration has failed. The server will start, but nothing will be
......
......@@ -20,14 +20,20 @@
#include <dhcp6/dhcp6_srv.h>
#include <dhcp/dhcp6.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option6_addrlst.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_int_array.h>
#include <dhcp/pkt6.h>
#include <dhcp/subnet.h>
#include <dhcp/cfgmgr.h>
#include <exceptions/exceptions.h>
#include <util/io_utilities.h>
#include <util/range_utilities.h>
#include <boost/foreach.hpp>
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
......@@ -51,10 +57,17 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
// First call to instance() will create IfaceMgr (it's a singleton)
// it may throw something if things go wrong
// Initialize objects required for DHCP server operation.
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) {
LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
shutdown_ = true;
......@@ -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)
}
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
void Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// add server-id
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) {
// TODO: question is currently unused, but we need to extract ORO from it
// and act on its content. Now we just send DNS-SERVERS option.
void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// Get the subnet for a particular address.
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
boost::shared_ptr<Option> dnsservers(new Option6AddrLst(D6O_NAME_SERVERS,
IOAddress(HARDCODED_DNS_SERVER)));
answer->addOption(dnsservers);
// Client requests some options using ORO option. Try to
// get this option from client's message.
boost::shared_ptr<Option6IntArray<uint16_t> > option_oro =
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) {
......@@ -428,3 +481,8 @@ Dhcpv6Srv::serverReceivedPacketName(uint8_t type) {
}
return (UNKNOWN);
}
void
Dhcpv6Srv::initStdOptionDefs() {
LibDHCP::initStdOptionDefs(Option::V6);
}
......@@ -19,6 +19,7 @@
#include <dhcp/dhcp6.h>
#include <dhcp/pkt6.h>
#include <dhcp/option.h>
#include <dhcp/option_definition.h>
#include <iostream>
namespace isc {
......@@ -170,8 +171,6 @@ protected:
/// @brief Appends requested options to 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 answer server's message (options will be added here)
......@@ -199,6 +198,18 @@ protected:
/// interfaces for new DUID generation are detected.
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)
boost::shared_ptr<isc::dhcp::Option> serverid_;
......
......@@ -17,14 +17,18 @@
#include <fstream>
#include <sstream>
#include <boost/foreach.hpp>
#include <arpa/inet.h>
#include <gtest/gtest.h>
#include <dhcp6/dhcp6_srv.h>
#include <dhcp6/config_parser.h>
#include <config/ccsession.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/subnet.h>
#include <dhcp/cfgmgr.h>
#include <dhcp/option6_ia.h>
using namespace std;
using namespace isc;
......@@ -43,6 +47,10 @@ public:
// deal with sockets here, just check if configuration handling
// is sane.
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() {
......@@ -60,6 +68,24 @@ public:
/// param value.
std::string createConfigWithOption(const std::string& param_value,
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;
stream << "{ \"interface\": [ \"all\" ],"
"\"preferred-lifetime\": 3000,"
......@@ -69,21 +95,21 @@ public:
" \"pool\": [ \"2001:db8:1::/80\" ],"
" \"subnet\": \"2001:db8:1::/64\", "
" \"option-data\": [ {";
if (parameter == "name") {
stream <<
" \"name\": \"" << param_value << "\","
" \"code\": 80,"
" \"data\": \"AB CDEF0105\"";
} else if (parameter == "code") {
stream <<
" \"name\": \"option_foo\","
" \"code\": " << param_value << ","
" \"data\": \"AB CDEF0105\"";
} else if (parameter == "data") {
stream <<
" \"name\": \"option_foo\","
" \"code\": 80,"
" \"data\": \"" << param_value << "\"";
bool first = true;
typedef std::pair<std::string, std::string> ParamPair;
BOOST_FOREACH(ParamPair param, params) {
if (!first) {
stream << ", ";
} else {
first = false;
}
if (param.first == "name") {
stream << "\"name\": \"" << param.second << "\"";
} else if (param.first == "code") {
stream << "\"code\": " << param.second << "";
} else if (param.first == "data") {
stream << "\"data\": \"" << param.second << "\"";
}
}
stream <<
" } ]"
......@@ -658,4 +684,58 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
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 @@
#include <arpa/inet.h>
#include <gtest/gtest.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/dhcp6_srv.h>
#include <dhcp/dhcp6.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/range_utilities.h>
#include <boost/scoped_ptr.hpp>
......@@ -31,6 +35,9 @@ using namespace std;
using namespace isc;
using namespace isc::dhcp;
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
// Maybe it should be isc::test?
......@@ -53,11 +60,14 @@ public:
class Dhcpv6SrvTest : public ::testing::Test {
public:
// these are empty for now, but let's keep them around
Dhcpv6SrvTest() {
Dhcpv6SrvTest()
: rcode_(-1) {
}
~Dhcpv6SrvTest() {
};
int rcode_;
ConstElementPtr comment_;
};
TEST_F(Dhcpv6SrvTest, basic) {
......@@ -66,7 +76,7 @@ TEST_F(Dhcpv6SrvTest, basic) {
// fe80::1234 link-local address on eth0 interface. Obviously
// an attempt to bind this socket will fail.
Dhcpv6Srv* srv = NULL;
ASSERT_NO_THROW( {
ASSERT_NO_THROW({
// open an unpriviledged port
srv = new Dhcpv6Srv(DHCP6_SERVER_PORT + 10000);
});
......@@ -78,7 +88,7 @@ TEST_F(Dhcpv6SrvTest, DUID) {
// tests that DUID is generated properly
boost::scoped_ptr<Dhcpv6Srv> srv;
ASSERT_NO_THROW( {
ASSERT_NO_THROW({
srv.reset(new Dhcpv6Srv(DHCP6_SERVER_PORT + 10000));
});
......@@ -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::fromJSON(config);
boost::scoped_ptr<NakedDhcpv6Srv> srv;
ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv()) );
ASSERT_NO_THROW(srv.reset(new NakedDhcpv6Srv()));
EXPECT_NO_THROW(x = configureDhcp6Server(*srv, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
ASSERT_EQ(0, rcode_);
// a dummy content for client-id
OptionBuffer clntDuid(32);
......@@ -169,10 +209,10 @@ TEST_F(Dhcpv6SrvTest, Solicit_basic) {
sol->addOption(ia);
// Let's not send address in solicit yet
// boost::shared_ptr<Option6IAAddr> addr(new Option6IAAddr(D6O_IAADDR,
// IOAddress("2001:db8:1234:ffff::ffff"), 5001, 7001));
// ia->addOption(addr);
// sol->addOption(ia);
/* boost::shared_ptr<Option6IAAddr>
addr(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1234:ffff::ffff"), 5001, 7001));
ia->addOption(addr);
sol->addOption(ia); */
// constructed very simple SOLICIT message with:
// - client-id option (mandatory)
......@@ -191,34 +231,83 @@ TEST_F(Dhcpv6SrvTest, Solicit_basic) {
boost::shared_ptr<Pkt6> reply = srv->processSolicit(sol);
// check if we get response at all
ASSERT_TRUE( reply != boost::shared_ptr<Pkt6>() );
EXPECT_EQ( DHCPV6_ADVERTISE, reply->getType() );
EXPECT_EQ( 1234, reply->getTransid() );
ASSERT_TRUE(reply);
EXPECT_EQ(DHCPV6_ADVERTISE, reply->getType());
EXPECT_EQ(1234, reply->getTransid());
// We have not requested option with code 1000 so it should not
// be included in the response.
ASSERT_FALSE(reply->getOption(1000));
// Let's now request option with code 1000.
// We expect that server will include this option in its reply.
boost::shared_ptr<Option6IntArray<uint16_t> >
option_oro(new Option6IntArray<uint16_t>(D6O_ORO));
// Create vector with one code equal to 1000.
std::vector<uint16_t> codes(1, 1000);
// Pass this code to option.
option_oro->setValues(codes);
// Append ORO to SOLICIT message.
sol->addOption(option_oro);
// Need to process SOLICIT again after requesting new option.
reply = srv->processSolicit(sol);
ASSERT_TRUE(reply);
EXPECT_EQ(DHCPV6_ADVERTISE, reply->getType());
OptionPtr tmp = reply->getOption(D6O_IA_NA);
ASSERT_TRUE( tmp );
ASSERT_TRUE(tmp);
Option6IA* reply_ia = dynamic_cast<Option6IA*>(tmp.get());
EXPECT_EQ( 234, reply_ia->getIAID() );
boost::shared_ptr<Option6IA> reply_ia =
boost::dynamic_pointer_cast<Option6IA>(tmp);
ASSERT_TRUE(reply_ia);
EXPECT_EQ(234, reply_ia->getIAID());
// check that there's an address included
EXPECT_TRUE( reply_ia->getOption(D6O_IAADDR));
EXPECT_TRUE(reply_ia->getOption(D6O_IAADDR));
// check that server included our own client-id
tmp = reply->getOption(D6O_CLIENTID);
ASSERT_TRUE( tmp );
EXPECT_EQ(clientid->getType(), tmp->getType() );
ASSERT_EQ(clientid->len(), tmp->len() );
ASSERT_TRUE(tmp);
EXPECT_EQ(clientid->getType(), tmp->getType());
ASSERT_EQ(clientid->len(), tmp->len());
EXPECT_TRUE( clientid->getData() == tmp->getData() );
EXPECT_TRUE(clientid->getData() == tmp->getData());
// check that server included its server-id
tmp = reply->getOption(D6O_SERVERID);
EXPECT_EQ(tmp->getType(), srv->getServerID()->getType() );
ASSERT_EQ(tmp->len(), srv->getServerID()->len() );
EXPECT_EQ(tmp->getType(), srv->getServerID()->getType());
ASSERT_EQ(tmp->len(), srv->getServerID()->len());
EXPECT_TRUE(tmp->getData() == srv->getServerID()->getData());
tmp = reply->getOption(D6O_NAME_SERVERS);
ASSERT_TRUE(tmp);
boost::shared_ptr<Option6AddrLst> reply_nameservers =
boost::dynamic_pointer_cast<Option6AddrLst>(tmp);
ASSERT_TRUE(reply_nameservers);
Option6AddrLst::AddressContainer addrs = reply_nameservers->getAddresses();
ASSERT_EQ(2, addrs.size());
EXPECT_TRUE(addrs[0] == IOAddress("2001:db8:1234:FFFF::1"));
EXPECT_TRUE(addrs[1] == IOAddress("2001:db8:1234:FFFF::2"));
// There is a dummy option with code 1000 we requested from a server.
</