Browse Source

[2325] Renew support implemented.

Tomek Mrugalski 12 years ago
parent
commit
650e0b501a
3 changed files with 295 additions and 15 deletions
  1. 109 8
      src/bin/dhcp6/dhcp6_srv.cc
  2. 26 4
      src/bin/dhcp6/dhcp6_srv.h
  3. 160 3
      src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

+ 109 - 8
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<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;
 }
 

+ 26 - 4
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<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

+ 160 - 3
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<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);
-    
+
 
 }