#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;
// 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) {
#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>
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?
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) {
}
}
-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++) {
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)
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
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
}