Commit 650e0b50 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[2325] Renew support implemented.

parent f634e891
......@@ -404,7 +404,7 @@ void Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
case OPTIONAL:
if (server_ids.size() > 1) {
isc_throw(RFCViolation, "Too many (" << server_ids.size()
<< ") client-id options received in " << pkt->getName());
<< ") server-id options received in " << pkt->getName());
}
}
}
......@@ -450,6 +450,9 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
OptionPtr opt_duid = question->getOption(D6O_CLIENTID);
if (opt_duid) {
duid = DuidPtr(new DUID(opt_duid->getData()));
} else {
// Let's drop the message. This client is not sane.
isc_throw(RFCViolation, "Mandatory client-id is missing in received message");
}
// Now that we have all information about the client, let's iterate over all
......@@ -462,7 +465,7 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
opt != question->options_.end(); ++opt) {
switch (opt->second->getType()) {
case D6O_IA_NA: {
OptionPtr answer_opt = handleIA_NA(subnet, duid, question,
OptionPtr answer_opt = assignIA_NA(subnet, duid, question,
boost::dynamic_pointer_cast<Option6IA>(opt->second));
if (answer_opt) {
answer->addOption(answer_opt);
......@@ -475,8 +478,8 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
}
}
OptionPtr Dhcpv6Srv::handleIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid, Pkt6Ptr question,
boost::shared_ptr<Option6IA> ia) {
OptionPtr Dhcpv6Srv::assignIA_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,
......@@ -565,12 +568,101 @@ OptionPtr Dhcpv6Srv::handleIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
return (ia_rsp);
}
Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
OptionPtr Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
Pkt6Ptr question, boost::shared_ptr<Option6IA> ia) {
Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(*duid, ia->getIAID(),
subnet->getID());
if (!sanityCheck(solicit, MANDATORY, FORBIDDEN)) {
return (Pkt6Ptr());
if (!lease) {
// client renewing a lease that we don't know about.
// 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 known leases for this duid/iaid."));
return (ia_rsp);
}
lease->preferred_lft_ = subnet->getPreferred();
lease->valid_lft_ = subnet->getValid();
lease->t1_ = subnet->getT1();
lease->t2_ = subnet->getT2();
lease->cltt_ = time(NULL);
LeaseMgrFactory::instance().updateLease6(lease);
// Create empty IA_NA option with IAID matching the request.
boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, 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);
return (ia_rsp);
}
void Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
// We need to renew addresses for all IA_NA options in the client's
// RENEW message.
// We need to select a subnet the client is connected in.
Subnet6Ptr subnet = selectSubnet(renew);
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).
// 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);
} else {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
.arg(subnet->toText());
}
// 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.
OptionPtr opt_duid = renew->getOption(D6O_CLIENTID);
if (!opt_duid) {
// This should not happen. We have checked this before.
reply->addOption(createStatusCode(STATUS_UnspecFail,
"You did not include mandatory client-id"));
return;
}
DuidPtr duid(new DUID(opt_duid->getData()));
for (Option::OptionCollection::iterator opt = renew->options_.begin();
opt != renew->options_.end(); ++opt) {
switch (opt->second->getType()) {
case D6O_IA_NA: {
OptionPtr answer_opt = renewIA_NA(subnet, duid, renew,
boost::dynamic_pointer_cast<Option6IA>(opt->second));
if (answer_opt) {
reply->addOption(answer_opt);
}
break;
}
default:
break;
}
}
}
Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
sanityCheck(solicit, MANDATORY, FORBIDDEN);
......@@ -602,8 +694,17 @@ Pkt6Ptr Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
}
Pkt6Ptr Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
/// @todo: Implement this
sanityCheck(renew, MANDATORY, MANDATORY);
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid()));
copyDefaultOptions(renew, reply);
appendDefaultOptions(renew, reply);
appendRequestedOptions(renew, reply);
renewLeases(renew, reply);
return reply;
}
......
......@@ -205,11 +205,24 @@ protected:
/// @param question client's message (typically SOLICIT or REQUEST)
/// @param ia pointer to client's IA_NA option (client's request)
/// @return IA_NA option (server's response)
OptionPtr handleIA_NA(const isc::dhcp::Subnet6Ptr& subnet,
OptionPtr assignIA_NA(const isc::dhcp::Subnet6Ptr& subnet,
const isc::dhcp::DuidPtr& duid,
isc::dhcp::Pkt6Ptr question,
boost::shared_ptr<Option6IA> ia);
/// @brief Renews specific IA_NA option
///
/// Generates response to IA_NA. This typically includes finding a lease that
/// corresponds to the received address. If no such lease is found, IA_NA
/// response is generates with appropriate status code.
///
/// @param subnet subnet the sender belongs to
/// @param duid client's duid
/// @param question client's message
/// @param ia IA_NA option that is being renewed
OptionPtr renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
Pkt6Ptr question, boost::shared_ptr<Option6IA> ia);
/// @brief Copies required options from client message to server answer.
///
/// Copies options that must appear in any server response (ADVERTISE, REPLY)
......@@ -240,14 +253,23 @@ protected:
/// @brief Assigns leases.
///
/// TODO: This method is currently a stub. It just appends one
/// hardcoded lease. It supports addresses (IA_NA) only. It does NOT
/// support temporary addresses (IA_TA) nor prefixes (IA_PD).
/// It supports addresses (IA_NA) only. It does NOT support temporary
/// addresses (IA_TA) nor prefixes (IA_PD).
///
/// @param question client's message (with requested IA_NA)
/// @param answer server's message (IA_NA options will be added here)
void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer);
/// @brief Attempts to renew received addresses
///
/// It iterates through received IA_NA options and attempts to renew
/// received addresses. If no such leases are found, proper status
/// code is added to reply message. Renewed addresses are added
/// as IA_NA/IAADDR to reply packet.
/// @param renew client's message asking for renew
/// @param reply server's response
void renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply);
/// @brief Sets server-identifier.
///
/// This method attempts to set server-identifier DUID. It loads it
......
......@@ -57,6 +57,7 @@ public:
using Dhcpv6Srv::processSolicit;
using Dhcpv6Srv::processRequest;
using Dhcpv6Srv::processRenew;
using Dhcpv6Srv::createStatusCode;
using Dhcpv6Srv::selectSubnet;
using Dhcpv6Srv::sanityCheck;
......@@ -618,7 +619,6 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
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.
......@@ -758,6 +758,163 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
cout << "Assigned address to client3=" << addr3->getAddress().toText() << endl;
}
// This test verifies that incoming (positive) RENEW can be handled properly, that a
// REPLY is generated, that the response has an address and that address
// really belongs to the configured pool and that lease is actually renewed.
//
// expected:
// - returned REPLY message has copy of client-id
// - returned REPLY message has server-id
// - returned REPLY message has IA that includes IAADDR
// - lease is actually renewed in LeaseMgr
TEST_F(Dhcpv6SrvTest, RenewBasic) {
boost::scoped_ptr<NakedDhcpv6Srv> srv;
ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
IOAddress addr("2001:db8:1:1::cafe:babe");
const uint32_t iaid = 234;
// generate client-id also duid_
OptionPtr clientid = generateClientId();
// Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
// value on purpose. They should be updated during RENEW.
Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid,
501, 502, 503, 504, subnet_->getID(), 0));
lease->cltt_ = 1234;
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
// check that the lease is really in the database
Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
ASSERT_TRUE(l);
// Check that T1, T2, preferred, valid and cltt really
EXPECT_NE(l->t1_, subnet_->getT1());
EXPECT_NE(l->t2_, subnet_->getT2());
EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
EXPECT_NE(l->valid_lft_, subnet_->getValid());
EXPECT_NE(l->cltt_, time(NULL));
// Let's create a RENEW
Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
req->setRemoteAddr(IOAddress("fe80::abcd"));
boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
ASSERT_TRUE(subnet_->inPool(addr));
OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
ia->addOption(renewed_addr_opt);
req->addOption(ia);
req->addOption(clientid);
// server-id is mandatory in RENEW
req->addOption(srv->getServerID());
// Pass it to the server and hope for a REPLY
Pkt6Ptr reply = srv->processRenew(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
boost::shared_ptr<Option6IAAddr> addr_opt = checkIA_NA(reply, 234, subnet_->getT1(),
subnet_->getT2());
// check that we've got the address we requested
checkIAAddr(addr_opt, addr, subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
checkServerId(reply, srv->getServerID());
checkClientId(reply, clientid);
// check that the lease is really in the database
l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt);
EXPECT_TRUE(l);
// Check that T1, T2, preferred, valid and cltt were really updated
EXPECT_EQ(l->t1_, subnet_->getT1());
EXPECT_EQ(l->t2_, subnet_->getT2());
EXPECT_EQ(l->preferred_lft_, subnet_->getPreferred());
EXPECT_EQ(l->valid_lft_, subnet_->getValid());
// checking for CLTT is a bit tricky if we want to avoid off by 1 errors
int32_t cltt = static_cast<int32_t>(l->cltt_);
int32_t expected = static_cast<int32_t>(time(NULL));
// 1 >= difference between cltt and expected
EXPECT_GE(1, abs(cltt - expected));
LeaseMgrFactory::instance().deleteLease6(addr_opt->getAddress());
}
// This test verifies that incoming (negative) RENEW can be handled properly.
// Client tries to RENEW an address that was never assigned to him.
// expected:
// - returned REPLY message has copy of client-id
// - returned REPLY message has server-id
// - returned REPLY message has IA that includes STATUS-CODE
// - No lease in LeaseMgr
TEST_F(Dhcpv6SrvTest, RenewNoLease) {
boost::scoped_ptr<NakedDhcpv6Srv> srv;
ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
IOAddress addr("2001:db8:1:1::dead");
const uint32_t iaid = 234;
// generate client-id also duid_
OptionPtr clientid = generateClientId();
// check that the lease is really in the database
Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
ASSERT_FALSE(l);
// Let's create a RENEW
Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
req->setRemoteAddr(IOAddress("fe80::abcd"));
boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
ASSERT_TRUE(subnet_->inPool(addr));
OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
ia->addOption(renewed_addr_opt);
req->addOption(ia);
req->addOption(clientid);
// server-id is mandatory in RENEW
req->addOption(srv->getServerID());
// Pass it to the server and hope for a REPLY
Pkt6Ptr reply = srv->processRenew(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
ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
ASSERT_TRUE(ia);
// Make sure there is no address assigned.
EXPECT_FALSE(ia->getOption(D6O_IAADDR));
// T1, T2 should be zeroed
EXPECT_EQ(0, ia->getT1());
EXPECT_EQ(0, ia->getT2());
OptionPtr status = ia->getOption(D6O_STATUS_CODE);
ASSERT_TRUE(status);
// We don't have dedicated class for status code, so let's just interpret
// first 2 bytes as status. Remainder of the status code option is just
// a text explanation what went wrong.
EXPECT_EQ(static_cast<uint16_t>(STATUS_NoAddrsAvail), status->getUint16());
l = LeaseMgrFactory::instance().getLease6(addr);
ASSERT_FALSE(l);
}
// This test verifies if the status code option is generated properly.
TEST_F(Dhcpv6SrvTest, StatusCode) {
boost::scoped_ptr<NakedDhcpv6Srv> srv;
......@@ -782,7 +939,7 @@ TEST_F(Dhcpv6SrvTest, sanityCheck) {
// check that the packets originating from local addresses can be
pkt->setRemoteAddr(IOAddress("fe80::abcd"));
// client-id is optional for information-request, so
// client-id is optional for information-request, so
EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL));
// empty packet, no client-id, no server-id
......@@ -836,7 +993,7 @@ TEST_F(Dhcpv6SrvTest, sanityCheck) {
RFCViolation);
EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
RFCViolation);
}
......
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