#include <exceptions/exceptions.h>
#include <util/io_utilities.h>
#include <util/range_utilities.h>
+#include <dhcp/duid.h>
+#include <dhcp/lease_mgr.h>
+#include <dhcp/cfgmgr.h>
+#include <dhcp/option6_iaaddr.h>
+
+// @todo: Replace this with MySQL_LeaseMgr (or a LeaseMgr factory)
+// once it is merged
+#include <dhcp/memfile_lease_mgr.h>
+ #include <boost/foreach.hpp>
+
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
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;
- return;
+ // Port 0 is used for testing purposes. It means that the server should
+ // not open any sockets at all. Some tests, e.g. configuration parser,
+ // require Dhcpv6Srv object, but they don't really need it to do
+ // anything. This speed up and simplifies the tests.
+ if (port > 0) {
+ if (IfaceMgr::instance().countIfaces() == 0) {
+ LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
+ shutdown_ = true;
+ return;
+ }
-
+ IfaceMgr::instance().openSockets6(port);
}
- IfaceMgr::instance().openSockets6(port);
-
setServerID();
- /// @todo: instantiate LeaseMgr here once it is imlpemented.
-
} catch (const std::exception &e) {
LOG_ERROR(dhcp6_logger, DHCP6_SRV_CONSTRUCT_ERROR).arg(e.what());
shutdown_ = true;
// 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
-
- // Add server-id.
+ 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.
+ OptionPtr 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);
+ }
+ }
}
+OptionPtr Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
+
+ // @todo: Implement Option6_StatusCode and rewrite this code here
+ vector<uint8_t> data(text.c_str(), text.c_str() + text.length());
+ data.insert(data.begin(), static_cast<uint8_t>(code % 256));
+ data.insert(data.begin(), static_cast<uint8_t>(code >> 8));
+ OptionPtr status(new Option(Option::V6, D6O_STATUS_CODE, data));
+ return (status);
+}
+
+Subnet6Ptr Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+
+ return (subnet);
+}
+
void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
- /// TODO Rewrite this once LeaseManager is implemented.
-
- // answer client's IA (this is mostly a dummy,
- // so let's answer only first IA and hope there is only one)
- boost::shared_ptr<Option> ia_opt = question->getOption(D6O_IA_NA);
- if (ia_opt) {
- // found IA
- Option* tmp = ia_opt.get();
- Option6IA* ia_req = dynamic_cast<Option6IA*>(tmp);
- if (ia_req) {
- boost::shared_ptr<Option6IA>
- ia_rsp(new Option6IA(D6O_IA_NA, ia_req->getIAID()));
- ia_rsp->setT1(HARDCODED_T1);
- ia_rsp->setT2(HARDCODED_T2);
- boost::shared_ptr<Option6IAAddr>
- addr(new Option6IAAddr(D6O_IAADDR,
- IOAddress(HARDCODED_LEASE),
- HARDCODED_PREFERRED_LIFETIME,
- HARDCODED_VALID_LIFETIME));
- ia_rsp->addOption(addr);
- answer->addOption(ia_rsp);
+
+ // We need to allocate addresses for all IA_NA options in the client's
+ // question (i.e. SOLICIT or REQUEST) message.
+
+ // We need to select a subnet the client is connected in.
+ Subnet6Ptr subnet = selectSubnet(question);
+ if (subnet) {
+ // This particular client is out of luck today. We do not have
+ // information about the subnet he is connected to. This likely means
+ // misconfiguration of the server (or some relays). We will continue to
+ // process this message, but our response will be almost useless: no
+ // addresses or prefixes, no subnet specific configuration etc. The only
+ // thing this client can get is some global information (like DNS
+ // servers).
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
+ .arg(subnet->toText());
+ } else {
+ // perhaps this should be logged on some higher level? This is most likely
+ // configuration bug.
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_SUBNET_SELECTION_FAILED);
+ }
+
+ // @todo: We should implement Option6Duid some day, but we can do without it
+ // just fine for now
+
+ // Let's find client's DUID. Client is supposed to include its client-id
+ // option almost all the time (the only exception is an anonymous inf-request,
+ // but that is mostly a theoretical case). Our allocation engine needs DUID
+ // and will refuse to allocate anything to anonymous clients.
+ DuidPtr duid;
+ OptionPtr opt_duid = question->getOption(D6O_CLIENTID);
+ if (opt_duid) {
+ duid = DuidPtr(new DUID(opt_duid->getData()));
+ }
+
+ // Now that we have all information about the client, let's iterate over all
+ // received options and handle IA_NA options one by one and store our
+ // responses in answer message (ADVERTISE or REPLY).
+ //
+ // @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();
+ opt != question->options_.end(); ++opt) {
+ switch (opt->second->getType()) {
+ case D6O_IA_NA: {
+ OptionPtr answer_opt = handleIA_NA(subnet, duid, question,
+ boost::dynamic_pointer_cast<Option6IA>(opt->second));
+ if (answer_opt) {
+ answer->addOption(answer_opt);
+ }
+ break;
}
+ default:
+ break;
+ }
+ }
+}
+
+OptionPtr Dhcpv6Srv::handleIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid, Pkt6Ptr question,
+ boost::shared_ptr<Option6IA> ia) {
+ // If there is no subnet selected for handling this IA_NA, the only thing to do left is
+ // to say that we are sorry, but the user won't get an address. As a convenience, we
+ // use a different status text to indicate that (compare to the same status code,
+ // but different wording below)
+ if (!subnet) {
+ // Create empty IA_NA option with IAID matching the request.
+ boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
+
+ // Insert status code NoAddrsAvail.
+ ia_rsp->addOption(createStatusCode(STATUS_NoAddrsAvail, "Sorry, no subnet available."));
+ return (ia_rsp);
+ }
+
+ // Check if the client sent us a hint in his IA_NA. Clients may send an
+ // address in their IA_NA options as a suggestion (e.g. the last address
+ // they used before).
+ shared_ptr<Option6IAAddr> hintOpt = dynamic_pointer_cast<Option6IAAddr>
+ (ia->getOption(D6O_IAADDR));
+ IOAddress hint("::");
+ if (hintOpt) {
+ hint = hintOpt->getAddress();
}
+
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PROCESS_IA_NA_REQUEST)
+ .arg(duid?duid->toText():"(no-duid)").arg(ia->getIAID())
+ .arg(hintOpt?hint.toText():"(no hint)");
+
+ // "Fake" allocation is processing of SOLICIT message. We pretend to do an
+ // allocation, but we do not put the lease in the database. That is ok,
+ // because we do not guarantee that the user will get that exact lease. If
+ // the user selects this server to do actual allocation (i.e. sends REQUEST)
+ // it should include this hint. That will help us during the actual lease
+ // allocation.
+ bool fake_allocation = false;
+ if (question->getType() == DHCPV6_SOLICIT) {
+ /// @todo: Check if we support rapid commit
+ fake_allocation = true;
+ }
+
+ // Use allocation engine to pick a lease for this client. Allocation engine
+ // will try to honour the hint, but it is just a hint - some other address
+ // may be used instead. If fake_allocation is set to false, the lease will
+ // be inserted into the LeaseMgr as well.
+ Lease6Ptr lease = alloc_engine_->allocateAddress6(subnet, duid, ia->getIAID(),
+ hint, fake_allocation);
+
+ // Create IA_NA that we will put in the response.
+ boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
+
+ if (lease) {
+ // We have a lease! Let's wrap its content into IA_NA option
+ // with IAADDR suboption.
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, fake_allocation?
+ DHCP6_LEASE_ADVERT:DHCP6_LEASE_ALLOC)
+ .arg(lease->addr_.toText())
+ .arg(duid?duid->toText():"(no-duid)")
+ .arg(ia->getIAID());
+
+ ia_rsp->setT1(subnet->getT1());
+ ia_rsp->setT2(subnet->getT2());
+
+ boost::shared_ptr<Option6IAAddr>
+ addr(new Option6IAAddr(D6O_IAADDR,
+ lease->addr_,
+ lease->preferred_lft_,
+ lease->valid_lft_));
+ ia_rsp->addOption(addr);
+
+ // It would be possible to insert status code=0(success) as well,
+ // but this is considered waste of bandwidth as absence of status
+ // code is considered a success.
+ } else {
+ // Allocation engine did not allocate a lease. The engine logged
+ // cause of that failure. The only thing left is to insert
+ // status code to pass the sad news to the client.
+
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, fake_allocation?
+ DHCP6_LEASE_ADVERT_FAIL:DHCP6_LEASE_ALLOC_FAIL)
+ .arg(duid?duid->toText():"(no-duid)")
+ .arg(ia->getIAID())
+ .arg(subnet->toText());
+
+ ia_rsp->addOption(createStatusCode(STATUS_NoAddrsAvail,
+ "Sorry, no address could be allocated."));
+ }
+ return (ia_rsp);
}
Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <iostream>
+ #include <config.h>
-#include <arpa/inet.h>
++
+ #include <fstream>
++#include <iostream>
+ #include <sstream>
+
+ #include <gtest/gtest.h>
-#include <dhcp6/config_parser.h>
-#include <dhcp6/dhcp6_srv.h>
+#include <asiolink/io_address.h>
++#include <boost/scoped_ptr.hpp>
++#include <config/ccsession.h>
++#include <dhcp/cfgmgr.h>
#include <dhcp/dhcp6.h>
-#include <dhcp/option6_ia.h>
+#include <dhcp/duid.h>
- #include <dhcp/cfgmgr.h>
++#include <dhcp/lease_mgr.h>
+#include <dhcp/option.h>
+ #include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+ #include <dhcp/option6_int_array.h>
-#include <config/ccsession.h>
++#include <dhcp6/config_parser.h>
++#include <dhcp6/dhcp6_srv.h>
+#include <dhcp6/dhcp6_srv.h>
#include <util/buffer.h>
#include <util/range_utilities.h>
- #include <dhcp/lease_mgr.h>
--#include <boost/scoped_ptr.hpp>
- #include <config.h>
- #include <iostream>
- #include <fstream>
- #include <sstream>
- #include <gtest/gtest.h>
--using namespace std;
++using namespace boost;
using namespace isc;
++using namespace isc::asiolink;
++using namespace isc::asiolink;
++using namespace isc::config;
++using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::util;
-using namespace isc::data;
-using namespace isc::config;
--using namespace isc::asiolink;
- using namespace boost;
++using namespace std;
// namespace has to be named, because friends are defined in Dhcpv6Srv class
// Maybe it should be isc::test?
class Dhcpv6SrvTest : public ::testing::Test {
public:
- Dhcpv6SrvTest()
- : rcode_(-1) {
+ // these are empty for now, but let's keep them around
- Dhcpv6SrvTest() {
++ Dhcpv6SrvTest() : rcode_(-1) {
+ subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 48, 1000,
+ 2000, 3000, 4000));
+ pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
+ subnet_->addPool6(pool_);
+
+ CfgMgr::instance().addSubnet6(subnet_);
+ }
+
+ // Generate IA_NA option with specified parameters
+ shared_ptr<Option6IA> generateIA(uint32_t iaid, uint32_t t1, uint32_t t2) {
+ shared_ptr<Option6IA> ia =
+ shared_ptr<Option6IA>(new Option6IA(D6O_IA_NA, iaid));
+ ia->setT1(t1);
+ ia->setT2(t2);
+ return (ia);
+ }
+
+ // Generate client-id option
+ OptionPtr generateClientId(size_t duid_size = 32) {
+
+ OptionBuffer clnt_duid(duid_size);
+ for (int i = 0; i < duid_size; i++) {
+ clnt_duid[i] = 100 + i;
+ }
+
+ duid_ = DuidPtr(new DUID(clnt_duid));
+
+ return (OptionPtr(new Option(Option::V6, D6O_CLIENTID,
+ clnt_duid.begin(),
+ clnt_duid.begin() + duid_size)));
+ }
+
+ // Checks if server response (ADVERTISE or REPLY) includes proper server-id.
+ void checkServerId(const Pkt6Ptr& rsp, const OptionPtr& expected_srvid) {
+ // check that server included its server-id
+ OptionPtr tmp = rsp->getOption(D6O_SERVERID);
+ EXPECT_EQ(tmp->getType(), expected_srvid->getType() );
+ ASSERT_EQ(tmp->len(), expected_srvid->len() );
+ EXPECT_TRUE(tmp->getData() == expected_srvid->getData());
+ }
+
+ // Checks if server response (ADVERTISE or REPLY) includes proper client-id.
+ void checkClientId(const Pkt6Ptr& rsp, const OptionPtr& expected_clientid) {
+ // check that server included our own client-id
+ OptionPtr tmp = rsp->getOption(D6O_CLIENTID);
+ ASSERT_TRUE(tmp);
+ EXPECT_EQ(expected_clientid->getType(), tmp->getType());
+ ASSERT_EQ(expected_clientid->len(), tmp->len());
+
+ // check that returned client-id is valid
+ EXPECT_TRUE(expected_clientid->getData() == tmp->getData());
+ }
+
+ // Checks that server response (ADVERTISE or REPLY) contains proper IA_NA option
+ // It returns IAADDR option for each chaining with checkIAAddr method.
+ shared_ptr<Option6IAAddr> checkIA_NA(const Pkt6Ptr& rsp, uint32_t expected_iaid,
+ uint32_t expected_t1, uint32_t expected_t2) {
+ OptionPtr tmp = rsp->getOption(D6O_IA_NA);
+ // Can't use ASSERT_TRUE() in method that returns something
+ if (!tmp) {
+ ADD_FAILURE() << "IA_NA option not present in response";
+ return (shared_ptr<Option6IAAddr>());
+ }
+
+ shared_ptr<Option6IA> ia = dynamic_pointer_cast<Option6IA>(tmp);
+ EXPECT_EQ(expected_iaid, ia->getIAID() );
+ EXPECT_EQ(expected_t1, ia->getT1());
+ EXPECT_EQ(expected_t2, ia->getT2());
+
+ tmp = ia->getOption(D6O_IAADDR);
+ shared_ptr<Option6IAAddr> addr = dynamic_pointer_cast<Option6IAAddr>(tmp);
+ return (addr);
+ }
+
+ // Check that generated IAADDR option contains expected address.
+ void checkIAAddr(shared_ptr<Option6IAAddr> addr, const IOAddress& expected_addr,
+ uint32_t expected_preferred, uint32_t expected_valid) {
+ // Check that the assigned address is indeed from the configured pool
+ EXPECT_TRUE(subnet_->inPool(addr->getAddress()));
+ EXPECT_EQ(expected_addr.toText(), addr->getAddress().toText());
+ EXPECT_EQ(addr->getPreferred(), subnet_->getPreferred());
+ EXPECT_EQ(addr->getValid(), subnet_->getValid());
}
+
+ // Basic checks for generated response (message type and transaction-id).
+ void checkResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
+ uint32_t expected_transid) {
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(expected_message_type, rsp->getType());
+ EXPECT_EQ(expected_transid, rsp->getTransid());
+ }
+
+ // Checks if the lease sent to client is present in the database
+ Lease6Ptr checkLease(const DuidPtr& duid, const OptionPtr& ia_na,
+ shared_ptr<Option6IAAddr> addr) {
+ shared_ptr<Option6IA> ia = dynamic_pointer_cast<Option6IA>(ia_na);
+
+ Lease6Ptr lease = LeaseMgr::instance().getLease6(addr->getAddress());
+ if (!lease) {
+ cout << "Lease for " << addr->getAddress().toText()
+ << " not found in the database backend.";
+ return (Lease6Ptr());
+ }
+
+ EXPECT_EQ(addr->getAddress().toText(), lease->addr_.toText());
+ EXPECT_TRUE(*lease->duid_ == *duid);
+ EXPECT_EQ(ia->getIAID(), lease->iaid_);
+ EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
+
+ return (lease);
+ }
+
~Dhcpv6SrvTest() {
+ CfgMgr::instance().deleteSubnets6();
};
+ // A subnet used in most tests
+ Subnet6Ptr subnet_;
+
+ // A pool used in most tests
+ Pool6Ptr pool_;
+
+ // A DUID used in most tests (typically as client-id)
+ DuidPtr duid_;
++
+ int rcode_;
+ ConstElementPtr comment_;
};
+// Test verifies that the Dhcpv6_srv class can be instantiated. It checks a mode
+// without open sockets and with sockets opened on a high port (to not require
+// root privileges).
TEST_F(Dhcpv6SrvTest, basic) {
// srv has stubbed interface detection. It will read
// interfaces.txt instead. It will pretend to have detected
// fe80::1234 link-local address on eth0 interface. Obviously
// an attempt to bind this socket will fail.
-- Dhcpv6Srv* srv = NULL;
++ boost::scoped_ptr<Dhcpv6Srv> srv;
++
+ ASSERT_NO_THROW( {
+ // Skip opening any sockets
- srv = new Dhcpv6Srv(0);
++ srv.reset(new Dhcpv6Srv(0));
+ });
-
- delete srv;
-
- ASSERT_NO_THROW( {
++ srv.reset();
+ ASSERT_NO_THROW({
// open an unpriviledged port
-- srv = new Dhcpv6Srv(DHCP6_SERVER_PORT + 10000);
++ srv.reset(new Dhcpv6Srv(DHCP6_SERVER_PORT + 10000));
});
-
-- delete srv;
}
+// Test checks that DUID is generated properly
TEST_F(Dhcpv6SrvTest, DUID) {
- // tests that DUID is generated properly
boost::scoped_ptr<Dhcpv6Srv> srv;
- ASSERT_NO_THROW({
- srv.reset(new Dhcpv6Srv(DHCP6_SERVER_PORT + 10000));
+ ASSERT_NO_THROW( {
+ srv.reset(new Dhcpv6Srv(0));
});
OptionPtr srvid = srv->getServerID();
}
}
-TEST_F(Dhcpv6SrvTest, solicitBasic) {
++TEST_F(Dhcpv6SrvTest, solicitBasic1) {
+ 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(0)));
+
+ 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++) {
+ clntDuid[i] = 100 + i;
+ }
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+
+ boost::shared_ptr<Option6IA> ia =
+ boost::shared_ptr<Option6IA>(new Option6IA(D6O_IA_NA, 234));
+ ia->setT1(1501);
+ ia->setT2(2601);
+ 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); */
+
+ // constructed very simple SOLICIT message with:
+ // - client-id option (mandatory)
+ // - IA option (a request for address, without any addresses)
+
+ // expected returned ADVERTISE message:
+ // - copy of client-id
+ // - server-id
+ // - IA that includes IAADDR
+
+ OptionPtr clientid = OptionPtr(new Option(Option::V6, D6O_CLIENTID,
+ clntDuid.begin(),
+ clntDuid.begin() + 16));
+ sol->addOption(clientid);
+
+ boost::shared_ptr<Pkt6> reply = srv->processSolicit(sol);
+
+ // check if we get response at all
+ 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);
+
+ 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));
+
+ // 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());
+
+ 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_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
+ }
+
++
+// There are no dedicated tests for Dhcpv6Srv::handleIA_NA and Dhcpv6Srv::assignLeases
+// as they are indirectly tested in Solicit and Request tests.
+
+// This test verifies that incoming SOLICIT can be handled properly, that an
+// ADVERTISE is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// This test sends a SOLICIT without any hint in IA_NA.
+//
+// constructed very simple SOLICIT message with:
+// - client-id option (mandatory)
+// - IA option (a request for address, without any addresses)
+//
+// expected returned ADVERTISE message:
+// - copy of client-id
+// - server-id
+// - IA that includes IAADDR
- TEST_F(Dhcpv6SrvTest, SolicitBasic) {
++TEST_F(Dhcpv6SrvTest, SolicitBasic2) {
+ boost::scoped_ptr<NakedDhcpv6Srv> srv;
+ ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+
+ sol->addOption(generateIA(234, 1500, 3000));
+
+ OptionPtr clientid = generateClientId();
+
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ Pkt6Ptr reply = srv->processSolicit(sol);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ // check that IA_NA was returned and that there's an address included
+ shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2());
+
+ // Check that the assigned address is indeed from the configured pool
+ checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
+
+ // check DUIDs
+ checkServerId(reply, srv->getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test verifies that incoming SOLICIT can be handled properly, that an
+// ADVERTISE is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// This test sends a SOLICIT with IA_NA that contains a valid hint.
+//
+// constructed very simple SOLICIT message with:
+// - client-id option (mandatory)
+// - IA option (a request for address, with an address that belongs to the
+// configured pool, i.e. is valid as hint)
+//
+// expected returned ADVERTISE message:
+// - copy of client-id
+// - server-id
+// - IA that includes IAADDR
+TEST_F(Dhcpv6SrvTest, SolicitHint) {
+ boost::scoped_ptr<NakedDhcpv6Srv> srv;
+ ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+
+ // Let's create a SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ shared_ptr<Option6IA> ia = generateIA(234, 1500, 3000);
+
+ // with a valid hint
+ IOAddress hint("2001:db8:1:1::dead:beef");
+ ASSERT_TRUE(subnet_->inPool(hint));
+ OptionPtr hint_opt(new Option6IAAddr(D6O_IAADDR, hint, 300, 500));
+ ia->addOption(hint_opt);
+ sol->addOption(ia);
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ Pkt6Ptr reply = srv->processSolicit(sol);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+
+ // check that IA_NA was returned and that there's an address included
+ shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2());
+
+ // check that we've got the address we requested
+ checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
+
+ // check DUIDs
+ checkServerId(reply, srv->getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test verifies that incoming SOLICIT can be handled properly, that an
+// ADVERTISE is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// This test sends a SOLICIT with IA_NA that contains an invalid hint.
+//
+// constructed very simple SOLICIT message with:
+// - client-id option (mandatory)
+// - IA option (a request for address, with an address that does not
+// belong to the configured pool, i.e. is valid as hint)
+//
+// expected returned ADVERTISE message:
+// - copy of client-id
+// - server-id
+// - IA that includes IAADDR
+TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
+ boost::scoped_ptr<NakedDhcpv6Srv> srv;
+ ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+
+ // Let's create a SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ shared_ptr<Option6IA> ia = generateIA(234, 1500, 3000);
+ IOAddress hint("2001:db8:1::cafe:babe");
+ ASSERT_FALSE(subnet_->inPool(hint));
+ OptionPtr hint_opt(new Option6IAAddr(D6O_IAADDR, hint, 300, 500));
+ ia->addOption(hint_opt);
+ sol->addOption(ia);
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ Pkt6Ptr reply = srv->processSolicit(sol);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ // check that IA_NA was returned and that there's an address included
+ shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2());
+
+ // Check that the assigned address is indeed from the configured pool
+ checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
+ EXPECT_TRUE(subnet_->inPool(addr->getAddress()));
+
+ // check DUIDs
+ checkServerId(reply, srv->getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test checks that the server is offering different addresses to different
+// clients in ADVERTISEs. Please note that ADVERTISE is not a guarantee that such
+// and address will be assigned. Had the pool was very small and contained only
+// 2 addresses, the third client would get the same advertise as the first one
+// and this is a correct behavior. It is REQUEST that will fail for the third
+// client. ADVERTISE is basically saying "if you send me a request, you will
+// probably get an address like this" (there are no guarantees).
+TEST_F(Dhcpv6SrvTest, ManySolicits) {
+ boost::scoped_ptr<NakedDhcpv6Srv> srv;
+ ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+
+ Pkt6Ptr sol1 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ Pkt6Ptr sol2 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 2345));
+ Pkt6Ptr sol3 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 3456));
+
+ sol1->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol2->setRemoteAddr(IOAddress("fe80::1223"));
+ sol3->setRemoteAddr(IOAddress("fe80::3467"));
+
+ sol1->addOption(generateIA(1, 1500, 3000));
+ sol2->addOption(generateIA(2, 1500, 3000));
+ sol3->addOption(generateIA(3, 1500, 3000));
+
+ // different client-id sizes
+ OptionPtr clientid1 = generateClientId(12);
+ OptionPtr clientid2 = generateClientId(14);
+ OptionPtr clientid3 = generateClientId(16);
+
+ sol1->addOption(clientid1);
+ sol2->addOption(clientid2);
+ sol3->addOption(clientid3);
+
+ // Pass it to the server and get an advertise
+ Pkt6Ptr reply1 = srv->processSolicit(sol1);
+ Pkt6Ptr reply2 = srv->processSolicit(sol2);
+ Pkt6Ptr reply3 = srv->processSolicit(sol3);
+
+ // check if we get response at all
+ checkResponse(reply1, DHCPV6_ADVERTISE, 1234);
+ checkResponse(reply2, DHCPV6_ADVERTISE, 2345);
+ checkResponse(reply3, DHCPV6_ADVERTISE, 3456);
+
+ // check that IA_NA was returned and that there's an address included
+ shared_ptr<Option6IAAddr> addr1 = checkIA_NA(reply1, 1, subnet_->getT1(),
+ subnet_->getT2());
+ shared_ptr<Option6IAAddr> addr2 = checkIA_NA(reply2, 2, subnet_->getT1(),
+ subnet_->getT2());
+ shared_ptr<Option6IAAddr> addr3 = checkIA_NA(reply3, 3, subnet_->getT1(),
+ subnet_->getT2());
+
+ // Check that the assigned address is indeed from the configured pool
+ checkIAAddr(addr1, addr1->getAddress(), subnet_->getPreferred(), subnet_->getValid());
+ checkIAAddr(addr2, addr2->getAddress(), subnet_->getPreferred(), subnet_->getValid());
+ checkIAAddr(addr3, addr3->getAddress(), subnet_->getPreferred(), subnet_->getValid());
+
+ // check DUIDs
+ checkServerId(reply1, srv->getServerID());
+ checkServerId(reply2, srv->getServerID());
+ checkServerId(reply3, srv->getServerID());
+ checkClientId(reply1, clientid1);
+ checkClientId(reply2, clientid2);
+ checkClientId(reply3, clientid3);
+
+ // Finally check that the addresses offered are different
+ EXPECT_NE(addr1->getAddress().toText(), addr2->getAddress().toText());
+ EXPECT_NE(addr2->getAddress().toText(), addr3->getAddress().toText());
+ EXPECT_NE(addr3->getAddress().toText(), addr1->getAddress().toText());
+ cout << "Offered address to client1=" << addr1->getAddress().toText() << endl;
+ cout << "Offered address to client2=" << addr2->getAddress().toText() << endl;
+ cout << "Offered address to client3=" << addr3->getAddress().toText() << endl;
+}
+
+
+// This test verifies that incoming REQUEST can be handled properly, that a
+// REPLY is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// This test sends a REQUEST with IA_NA that contains a valid hint.
+//
+// constructed very simple REQUEST message with:
+// - client-id option (mandatory)
+// - IA option (a request for address, with an address that belongs to the
+// configured pool, i.e. is valid as hint)
+//
+// expected returned REPLY message:
+// - copy of client-id
+// - server-id
+// - IA that includes IAADDR
+TEST_F(Dhcpv6SrvTest, RequestBasic) {
+ boost::scoped_ptr<NakedDhcpv6Srv> srv;
+ ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+
+ // Let's create a REQUEST
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ shared_ptr<Option6IA> ia = generateIA(234, 1500, 3000);
+
+ // with a valid hint
+ IOAddress hint("2001:db8:1:1::dead:beef");
+ ASSERT_TRUE(subnet_->inPool(hint));
+ OptionPtr hint_opt(new Option6IAAddr(D6O_IAADDR, hint, 300, 500));
+ ia->addOption(hint_opt);
+ req->addOption(ia);
+ OptionPtr clientid = generateClientId();
+ req->addOption(clientid);
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv->processRequest(req);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+
+ // check that IA_NA was returned and that there's an address included
+ shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2());
+
+ // check that we've got the address we requested
+ checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
+
+ // check DUIDs
+ checkServerId(reply, srv->getServerID());
+ checkClientId(reply, clientid);
+
+ // check that the lease is really in the database
+ Lease6Ptr l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr);
+ EXPECT_TRUE(l);
+ LeaseMgr::instance().deleteLease6(addr->getAddress());
+}
+
+// This test checks that the server is offering different addresses to different
+// clients in REQUEST. Please note that ADVERTISE is not a guarantee that such
+// and address will be assigned. Had the pool was very small and contained only
+// 2 addresses, the third client would get the same advertise as the first one
+// and this is a correct behavior. It is REQUEST that will fail for the third
+// client. ADVERTISE is basically saying "if you send me a request, you will
+// probably get an address like this" (there are no guarantees).
+TEST_F(Dhcpv6SrvTest, ManyRequests) {
+ boost::scoped_ptr<NakedDhcpv6Srv> srv;
+ ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+
+ Pkt6Ptr req1 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
+ Pkt6Ptr req2 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 2345));
+ Pkt6Ptr req3 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 3456));
+
+ req1->setRemoteAddr(IOAddress("fe80::abcd"));
+ req2->setRemoteAddr(IOAddress("fe80::1223"));
+ req3->setRemoteAddr(IOAddress("fe80::3467"));
+
+ req1->addOption(generateIA(1, 1500, 3000));
+ req2->addOption(generateIA(2, 1500, 3000));
+ req3->addOption(generateIA(3, 1500, 3000));
+
+ // different client-id sizes
+ OptionPtr clientid1 = generateClientId(12);
+ OptionPtr clientid2 = generateClientId(14);
+ OptionPtr clientid3 = generateClientId(16);
+
+ req1->addOption(clientid1);
+ req2->addOption(clientid2);
+ req3->addOption(clientid3);
+
+ // Pass it to the server and get an advertise
+ Pkt6Ptr reply1 = srv->processRequest(req1);
+ Pkt6Ptr reply2 = srv->processRequest(req2);
+ Pkt6Ptr reply3 = srv->processRequest(req3);
+
+ // check if we get response at all
+ checkResponse(reply1, DHCPV6_REPLY, 1234);
+ checkResponse(reply2, DHCPV6_REPLY, 2345);
+ checkResponse(reply3, DHCPV6_REPLY, 3456);
+
+ // check that IA_NA was returned and that there's an address included
+ shared_ptr<Option6IAAddr> addr1 = checkIA_NA(reply1, 1, subnet_->getT1(),
+ subnet_->getT2());
+ shared_ptr<Option6IAAddr> addr2 = checkIA_NA(reply2, 2, subnet_->getT1(),
+ subnet_->getT2());
+ shared_ptr<Option6IAAddr> addr3 = checkIA_NA(reply3, 3, subnet_->getT1(),
+ subnet_->getT2());
+
+ // Check that the assigned address is indeed from the configured pool
+ checkIAAddr(addr1, addr1->getAddress(), subnet_->getPreferred(), subnet_->getValid());
+ checkIAAddr(addr2, addr2->getAddress(), subnet_->getPreferred(), subnet_->getValid());
+ checkIAAddr(addr3, addr3->getAddress(), subnet_->getPreferred(), subnet_->getValid());
+
+ // check DUIDs
+ checkServerId(reply1, srv->getServerID());
+ checkServerId(reply2, srv->getServerID());
+ checkServerId(reply3, srv->getServerID());
+ checkClientId(reply1, clientid1);
+ checkClientId(reply2, clientid2);
+ checkClientId(reply3, clientid3);
+
+ // Finally check that the addresses offered are different
+ EXPECT_NE(addr1->getAddress().toText(), addr2->getAddress().toText());
+ EXPECT_NE(addr2->getAddress().toText(), addr3->getAddress().toText());
+ EXPECT_NE(addr3->getAddress().toText(), addr1->getAddress().toText());
+ cout << "Assigned address to client1=" << addr1->getAddress().toText() << endl;
+ cout << "Assigned address to client2=" << addr2->getAddress().toText() << endl;
+ cout << "Assigned address to client3=" << addr3->getAddress().toText() << endl;
+}
+
+
TEST_F(Dhcpv6SrvTest, serverReceivedPacketName) {
// Check all possible packet types
for (int itype = 0; itype < 256; ++itype) {