From: Tomek Mrugalski Date: Mon, 3 Dec 2012 19:07:24 +0000 (+0100) Subject: [2325] Renew support implemented. X-Git-Tag: bind10-1.0.0-beta-release~29^2~2^2^2~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=650e0b501ab48af61704efe9cb503490716c8eec;p=thirdparty%2Fkea.git [2325] Renew support implemented. --- diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 38bf46f09d..5edb6eab23 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -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(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 ia) { +OptionPtr Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid, + Pkt6Ptr question, boost::shared_ptr 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 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 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 ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID())); + + ia_rsp->setT1(subnet->getT1()); + ia_rsp->setT2(subnet->getT2()); + + boost::shared_ptr 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(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; } diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index 782e7a8981..8490d9b8b3 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -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 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 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 diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc index 24273d3e0e..7950615222 100644 --- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc +++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc @@ -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 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 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 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(l->cltt_); + int32_t expected = static_cast(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 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 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(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(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 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); - + }