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

[2417] Append configured DHCPv6 options to server's response.

parent 38ebe09e
......@@ -602,7 +602,7 @@ private:
<< " be equal to zero. Option code '0' is reserved in"
<< " DHCPv6.");
} else if (option_code > std::numeric_limits<uint16_t>::max()) {
isc_throw(Dhcp6ConfigError, "Parser error: value of 'code' must not"ciwtezcowy
isc_throw(Dhcp6ConfigError, "Parser error: value of 'code' must not"
<< " exceed " << std::numeric_limits<uint16_t>::max());
}
// Check the option name has been specified, is non-empty and does not
......
......@@ -110,6 +110,10 @@ 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_FOR_ADDRESS fail to find subnet for address: %1
This warning message indicates that server does not support subnet
that received DHCPv6 packet comes from.
% 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
......
......@@ -24,11 +24,16 @@
#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;
......@@ -289,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_FOR_ADDRESS)
.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_FOR_ADDRESS)
.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) {
......
......@@ -171,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)
......
......@@ -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) {
......@@ -150,10 +160,40 @@ 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()) );
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);
for (int i = 0; i < 32; i++) {
......@@ -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,15 +231,38 @@ 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>() );
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 );
Option6IA* reply_ia = dynamic_cast<Option6IA*>(tmp.get());
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
......@@ -219,6 +282,32 @@ TEST_F(Dhcpv6SrvTest, Solicit_basic) {
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.
// Expect that this option is in server's response.
tmp = reply->getOption(1000);
ASSERT_TRUE(tmp);
// Check that the option contains valid data (from configuration).
std::vector<uint8_t> data = tmp->getData();
ASSERT_EQ(2, data.size());
const uint8_t foo_expected[] = {
0x12, 0x34
};
EXPECT_EQ(0, memcmp(&data[0], foo_expected, 2));
// more checks to be implemented
}
......
......@@ -106,6 +106,11 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
buf.begin() + offset,
buf.begin() + offset + opt_len));
break;
case D6O_ORO:
opt = OptionPtr(new Option6IntArray<uint16_t>(opt_type,
buf.begin() + offset,
buf.begin() + offset + opt_len));
break;
default:
opt = OptionPtr(new Option(Option::V6,
opt_type,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment