BIND 10 master, updated. 92758ecffc245b5f9a86dbc8a2201a9c1397f7d8 [master] Merge branch 'master' into trac2325 (RENEW support in DHCPv6)

BIND 10 source code commits bind10-changes at lists.isc.org
Wed Dec 12 13:17:50 UTC 2012


The branch, master has been updated
       via  92758ecffc245b5f9a86dbc8a2201a9c1397f7d8 (commit)
       via  7f6c9d057cc0a7a10f41ce7da9c8565b9ee85246 (commit)
       via  a40fac0707fac08e85b06974092bbdc15720f37f (commit)
       via  196c4d239ece786aa29b86e6b48bef337ca330fc (commit)
       via  1f943baf9448dcec53f5bcde3e9d21fae2eeb121 (commit)
       via  d6fb00a3198fe327cdbf4ebfe14f47eb23086325 (commit)
       via  f2a03c4538343e2a6941e5037925a63a7e71f68c (commit)
       via  936137b0b10508b395cbd4a71ad07c1ec0ffb655 (commit)
       via  cacc927a7096327c8e19479058258cecec0eb27e (commit)
       via  650e0b501ab48af61704efe9cb503490716c8eec (commit)
       via  f634e891b21b4533c16871bab7bcd65f6422986d (commit)
       via  159af7d5de17a74269cd4af69ac3b569495c4a4e (commit)
       via  54663a3c7bda67a6813d2478ff44c8a07d9c48b8 (commit)
       via  00eee13bde95c741890ad3ca52967c11076c5087 (commit)
       via  7793f98f619c3422ca3fa26795a5861b0fbab4fa (commit)
      from  a56f7ff8c099d2d97a177cba073ee413ba3e9bd9 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 92758ecffc245b5f9a86dbc8a2201a9c1397f7d8
Merge: a56f7ff 7f6c9d0
Author: Tomek Mrugalski <tomasz at isc.org>
Date:   Wed Dec 12 14:04:58 2012 +0100

    [master] Merge branch 'master' into trac2325 (RENEW support in DHCPv6)
    
    Conflicts:
    	ChangeLog

-----------------------------------------------------------------------

Summary of changes:
 ChangeLog                                 |    5 +
 src/bin/dhcp6/dhcp6_messages.mes          |   27 +++
 src/bin/dhcp6/dhcp6_srv.cc                |  297 +++++++++++++++++--------
 src/bin/dhcp6/dhcp6_srv.h                 |   80 +++++--
 src/bin/dhcp6/tests/dhcp6_srv_unittest.cc |  333 +++++++++++++++++++++++++----
 src/lib/dhcp/option.cc                    |    4 +
 src/lib/dhcp/option.h                     |   17 +-
 src/lib/dhcp/option6_ia.h                 |    9 +-
 src/lib/dhcp/pkt6.cc                      |   72 ++++++-
 src/lib/dhcp/pkt6.h                       |   45 +++-
 src/lib/dhcp/tests/option6_ia_unittest.cc |   11 +-
 src/lib/dhcp/tests/option_unittest.cc     |   23 ++
 src/lib/dhcp/tests/pkt6_unittest.cc       |   73 +++++++
 src/lib/dhcpsrv/subnet.h                  |    2 +-
 14 files changed, 827 insertions(+), 171 deletions(-)

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 5663c9b..06207ec 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+524.	[func]		tomek
+	b10-dhcp6 is now able to handle RENEW messages. Leases are
+	renewed and REPLY responses are sent back to clients.
+	(Trac #2325, git 7f6c9d057cc0a7a10f41ce7da9c8565b9ee85246)
+
 523.	[bug]		muks
 	Fixed a problem in inmem NSEC3 lookup (for, instance when using a
 	zone with no non-apex names) which caused exceptions when the zone
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index 6b82344..6ab42b3 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -22,6 +22,11 @@ successfully established a session with the BIND 10 control channel.
 This debug message is issued just before the IPv6 DHCP server attempts
 to establish a session with the BIND 10 control channel.
 
+% DHCP6_CLIENTID_MISSING mandatory client-id option is missing, message from %1 dropped
+This error message indicates that the received message is being dropped
+because it does not include the mandatory client-id option necessary for
+address assignment.  The most likely cause is a problem with the client.
+
 % DHCP6_COMMAND_RECEIVED received command %1, arguments: %2
 A debug message listing the command (and possible arguments) received
 from the BIND 10 control system by the IPv6 DHCP server.
@@ -81,6 +86,13 @@ This message indicates that the server failed to grant (in response to
 received REQUEST) a lease for a given client. There may be many reasons for
 such failure. Each specific failure is logged in a separate log entry.
 
+% DHCP6_REQUIRED_OPTIONS_CHECK_FAIL %1 message received from %2 failed the following check: %3
+This message indicates that received DHCPv6 packet is invalid.  This may be due
+to a number of reasons, e.g. the mandatory client-id option is missing,
+the server-id forbidden in that particular type of message is present,
+there is more than one instance of client-id or server-id present,
+etc. The exact reason for rejecting the packet is included in the message.
+
 % DHCP6_NOT_RUNNING IPv6 DHCP server is not running
 A warning message is issued when an attempt is made to shut down the
 IPv6 DHCP server but it is not running.
@@ -189,3 +201,18 @@ which the DHCPv6 server has not been configured. The cause is most likely due
 to a misconfiguration of the server. The packet processing will continue, but
 the response will only contain generic configuration parameters and no
 addresses or prefixes.
+
+% DHCP6_UNKNOWN_RENEW received RENEW from client (duid=%1, iaid=%2) in subnet %3
+This warning message is printed when client attempts to renew a lease,
+but no such lease is known by the server. It typically means that
+client has attempted to use its lease past its lifetime: causes of this
+include a adjustment of the clients date/time setting or poor support
+for sleep/recovery. A properly implemented client will recover from such
+a situation by restarting the lease allocation process after receiving
+a negative reply from the server.
+
+An alternative cause could be that the server has lost its database
+recently and does not recognize its well-behaving clients. This is more
+probable if you see many such messages. Clients will recover from this,
+but they will most likely get a different IP addresses and experience
+a brief service interruption.
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 33bab99..2482833 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -127,50 +127,57 @@ bool Dhcpv6Srv::run() {
                 continue;
             }
             LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
-                      .arg(serverReceivedPacketName(query->getType()));
+                      .arg(query->getName());
             LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
                       .arg(static_cast<int>(query->getType()))
                       .arg(query->getBuffer().getLength())
                       .arg(query->toText());
 
-            switch (query->getType()) {
-            case DHCPV6_SOLICIT:
-                rsp = processSolicit(query);
-                break;
+            try {
+                switch (query->getType()) {
+                case DHCPV6_SOLICIT:
+                    rsp = processSolicit(query);
+                    break;
 
-            case DHCPV6_REQUEST:
-                rsp = processRequest(query);
-                break;
+                case DHCPV6_REQUEST:
+                    rsp = processRequest(query);
+                    break;
 
-            case DHCPV6_RENEW:
-                rsp = processRenew(query);
-                break;
+                case DHCPV6_RENEW:
+                    rsp = processRenew(query);
+                    break;
 
-            case DHCPV6_REBIND:
-                rsp = processRebind(query);
-                break;
+                case DHCPV6_REBIND:
+                    rsp = processRebind(query);
+                    break;
 
-            case DHCPV6_CONFIRM:
-                rsp = processConfirm(query);
-                break;
+                case DHCPV6_CONFIRM:
+                    rsp = processConfirm(query);
+                    break;
 
-            case DHCPV6_RELEASE:
+                case DHCPV6_RELEASE:
                 rsp = processRelease(query);
                 break;
 
-            case DHCPV6_DECLINE:
-                rsp = processDecline(query);
-                break;
+                case DHCPV6_DECLINE:
+                    rsp = processDecline(query);
+                    break;
 
-            case DHCPV6_INFORMATION_REQUEST:
-                rsp = processInfRequest(query);
-                break;
+                case DHCPV6_INFORMATION_REQUEST:
+                    rsp = processInfRequest(query);
+                    break;
 
-            default:
-                // Only action is to output a message if debug is enabled,
-                // and that will be covered by the debug statement before
-                // the "switch" statement.
-                ;
+                default:
+                    // Only action is to output a message if debug is enabled,
+                    // and that will be covered by the debug statement before
+                    // the "switch" statement.
+                    ;
+                }
+            } catch (const RFCViolation& e) {
+                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_REQUIRED_OPTIONS_CHECK_FAIL)
+                    .arg(query->getName())
+                    .arg(query->getRemoteAddr())
+                    .arg(e.what());
             }
 
             if (rsp) {
@@ -196,9 +203,6 @@ bool Dhcpv6Srv::run() {
                 }
             }
         }
-
-        // TODO add support for config session (see src/bin/auth/main.cc)
-        //      so this daemon can be controlled from bob
     }
 
     return (true);
@@ -374,6 +378,54 @@ OptionPtr Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
     return (option_status);
 }
 
+void Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
+                            RequirementLevel serverid) {
+    Option::OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
+    switch (clientid) {
+    case MANDATORY:
+        if (client_ids.size() != 1) {
+            isc_throw(RFCViolation, "Exactly 1 client-id option expected in "
+                      << pkt->getName() << ", but " << client_ids.size()
+                      << " received");
+        }
+        break;
+    case OPTIONAL:
+        if (client_ids.size() > 1) {
+            isc_throw(RFCViolation, "Too many (" << client_ids.size()
+                      << ") client-id options received in " << pkt->getName());
+        }
+        break;
+
+    case FORBIDDEN:
+        // doesn't make sense - client-id is always allowed
+        break;
+    }
+
+    Option::OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
+    switch (serverid) {
+    case FORBIDDEN:
+        if (server_ids.size() > 0) {
+            isc_throw(RFCViolation, "Exactly 1 server-id option expected, but "
+                      << server_ids.size() << " received in " << pkt->getName());
+        }
+        break;
+
+    case MANDATORY:
+        if (server_ids.size() != 1) {
+            isc_throw(RFCViolation, "Invalid number of server-id options received ("
+                      << server_ids.size() << "), exactly 1 expected in message "
+                      << pkt->getName());
+        }
+        break;
+
+    case OPTIONAL:
+        if (server_ids.size() > 1) {
+            isc_throw(RFCViolation, "Too many (" << server_ids.size()
+                      << ") server-id options received in " << pkt->getName());
+        }
+    }
+}
+
 Subnet6Ptr Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
 
@@ -387,7 +439,7 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
 
     // We need to select a subnet the client is connected in.
     Subnet6Ptr subnet = selectSubnet(question);
-    if (subnet) {
+    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
@@ -395,12 +447,13 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
         // 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);
+        LOG_ERROR(dhcp6_logger, DHCP6_SUBNET_SELECTION_FAILED);
+    } else {
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
+            .arg(subnet->toText());
     }
 
     // @todo: We should implement Option6Duid some day, but we can do without it
@@ -414,6 +467,10 @@ 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 {
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLIENTID_MISSING);
+        // 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
@@ -426,7 +483,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);
@@ -439,8 +496,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,
@@ -535,8 +592,111 @@ OptionPtr Dhcpv6Srv::handleIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
     return (ia_rsp);
 }
 
+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 (!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."));
+
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_UNKNOWN_RENEW)
+            .arg(duid->toText())
+            .arg(ia->getIAID())
+            .arg(subnet->toText());
+
+        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_ERROR(dhcp6_logger, 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);
+
     Pkt6Ptr advertise(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
 
     copyDefaultOptions(solicit, advertise);
@@ -549,6 +709,9 @@ Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
 }
 
 Pkt6Ptr Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
+
+    sanityCheck(request, MANDATORY, MANDATORY);
+
     Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid()));
 
     copyDefaultOptions(request, reply);
@@ -561,8 +724,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;
 }
 
@@ -596,48 +768,5 @@ Pkt6Ptr Dhcpv6Srv::processInfRequest(const Pkt6Ptr& infRequest) {
     return reply;
 }
 
-const char*
-Dhcpv6Srv::serverReceivedPacketName(uint8_t type) {
-    static const char* CONFIRM = "CONFIRM";
-    static const char* DECLINE = "DECLINE";
-    static const char* INFORMATION_REQUEST = "INFORMATION_REQUEST";
-    static const char* REBIND = "REBIND";
-    static const char* RELEASE = "RELEASE";
-    static const char* RENEW = "RENEW";
-    static const char* REQUEST = "REQUEST";
-    static const char* SOLICIT = "SOLICIT";
-    static const char* UNKNOWN = "UNKNOWN";
-
-    switch (type) {
-    case DHCPV6_CONFIRM:
-        return (CONFIRM);
-
-    case DHCPV6_DECLINE:
-        return (DECLINE);
-
-    case DHCPV6_INFORMATION_REQUEST:
-        return (INFORMATION_REQUEST);
-
-    case DHCPV6_REBIND:
-        return (REBIND);
-
-    case DHCPV6_RELEASE:
-        return (RELEASE);
-
-    case DHCPV6_RENEW:
-        return (RENEW);
-
-    case DHCPV6_REQUEST:
-        return (REQUEST);
-
-    case DHCPV6_SOLICIT:
-        return (SOLICIT);
-
-    default:
-        ;
-    }
-    return (UNKNOWN);
-}
-
 };
 };
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index de9ee36..b6b0306 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -30,6 +30,23 @@
 
 namespace isc {
 namespace dhcp {
+
+/// An exception that is thrown if a DHCPv6 protocol violation occurs while
+/// processing a message (e.g. a mandatory option is missing)
+class RFCViolation : public isc::Exception {
+public:
+
+/// @brief constructor
+///
+/// @param file name of the file, where exception occurred
+/// @param line line of the file, where exception occurred
+/// @param what text description of the issue that caused exception
+RFCViolation(const char* file, size_t line, const char* what) :
+    isc::Exception(file, line, what) {}
+};
+
+
+
 /// @brief DHCPv6 server service.
 ///
 /// This class represents DHCPv6 server. It contains all
@@ -45,6 +62,12 @@ namespace dhcp {
 class Dhcpv6Srv : public boost::noncopyable {
 
 public:
+    /// @brief defines if certain option may, must or must not appear
+    typedef enum {
+        FORBIDDEN,
+        MANDATORY,
+        OPTIONAL
+    } RequirementLevel;
 
     /// @brief Minimum length of a MAC address to be used in DUID generation.
     static const size_t MIN_MAC_LEN = 6;
@@ -83,24 +106,20 @@ public:
     /// @brief Instructs the server to shut down.
     void shutdown();
 
-    /// @brief Return textual type of packet received by server.
-    ///
-    /// Returns the name of valid packet received by the server (e.g. SOLICIT).
-    /// If the packet is unknown - or if it is a valid DHCP packet but not one
-    /// expected to be received by the server (such as an ADVERTISE), the string
-    /// "UNKNOWN" is returned.  This method is used in debug messages.
-    ///
-    /// As the operation of the method does not depend on any server state, it
-    /// is declared static.
+protected:
+
+    /// @brief verifies if specified packet meets RFC requirements
     ///
-    /// @param type DHCPv4 packet type
+    /// Checks if mandatory option is really there, that forbidden option
+    /// is not there, and that client-id or server-id appears only once.
     ///
-    /// @return Pointer to "const" string containing the packet name.
-    ///         Note that this string is statically allocated and MUST NOT
-    ///         be freed by the caller.
-    static const char* serverReceivedPacketName(uint8_t type);
+    /// @param pkt packet to be checked
+    /// @param clientid expectation regarding client-id option
+    /// @param serverid expectation regarding server-id option
+    /// @throw RFCViolation if any issues are detected
+    void sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
+                     RequirementLevel serverid);
 
-protected:
     /// @brief Processes incoming SOLICIT and returns response.
     ///
     /// Processes received SOLICIT message and verifies that its sender
@@ -186,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, an IA_NA
+    /// response is generated with an 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)
@@ -221,14 +253,24 @@ 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).
+    /// @todo: Extend this method once TA and PD becomes supported
     ///
     /// @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 6a598eb..03bf309 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -19,6 +19,7 @@
 #include <dhcp/dhcp6.h>
 #include <dhcp/duid.h>
 #include <dhcp/option.h>
+#include <dhcp/option_custom.h>
 #include <dhcp/option6_addrlst.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_iaaddr.h>
@@ -57,8 +58,10 @@ public:
 
     using Dhcpv6Srv::processSolicit;
     using Dhcpv6Srv::processRequest;
+    using Dhcpv6Srv::processRenew;
     using Dhcpv6Srv::createStatusCode;
     using Dhcpv6Srv::selectSubnet;
+    using Dhcpv6Srv::sanityCheck;
 };
 
 class Dhcpv6SrvTest : public ::testing::Test {
@@ -139,6 +142,33 @@ public:
         return (addr);
     }
 
+    // Checks that server rejected IA_NA, i.e. that it has no addresses and
+    // that expected status code really appears there.
+    // Status code indicates type of error encountered (in theory it can also
+    // indicate success, but servers typically don't send success status
+    // as this is the default result and it saves bandwidth)
+    void checkRejectedIA_NA(const boost::shared_ptr<Option6IA>& ia,
+                            uint16_t expected_status_code) {
+        // 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());
+
+        boost::shared_ptr<OptionCustom> status =
+            boost::dynamic_pointer_cast<OptionCustom>(ia->getOption(D6O_STATUS_CODE));
+        EXPECT_TRUE(status);
+
+        if (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 content is
+            // just a text explanation what went wrong.
+            EXPECT_EQ(static_cast<uint16_t>(expected_status_code),
+                      status->readInteger<uint16_t>(0));
+        }
+    }
+
     // Check that generated IAADDR option contains expected address.
     void checkIAAddr(const boost::shared_ptr<Option6IAAddr>& addr,
                      const IOAddress& expected_addr,
@@ -617,7 +647,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.
@@ -651,6 +680,9 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
     OptionPtr clientid = generateClientId();
     req->addOption(clientid);
 
+    // server-id is mandatory in REQUEST
+    req->addOption(srv->getServerID());
+
     // Pass it to the server and hope for a REPLY
     Pkt6Ptr reply = srv->processRequest(req);
 
@@ -709,6 +741,11 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
     req2->addOption(clientid2);
     req3->addOption(clientid3);
 
+    // server-id is mandatory in REQUEST
+    req1->addOption(srv->getServerID());
+    req2->addOption(srv->getServerID());
+    req3->addOption(srv->getServerID());
+
     // Pass it to the server and get an advertise
     Pkt6Ptr reply1 = srv->processRequest(req1);
     Pkt6Ptr reply2 = srv->processRequest(req2);
@@ -749,50 +786,207 @@ 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)) );
 
-TEST_F(Dhcpv6SrvTest, serverReceivedPacketName) {
-    // Check all possible packet types
-    for (int itype = 0; itype < 256; ++itype) {
-        uint8_t type = itype;
+    const IOAddress addr("2001:db8:1:1::cafe:babe");
+    const uint32_t iaid = 234;
 
-        switch (type) {
-        case DHCPV6_CONFIRM:
-            EXPECT_STREQ("CONFIRM", Dhcpv6Srv::serverReceivedPacketName(type));
-            break;
+    // Generate client-id also duid_
+    OptionPtr clientid = generateClientId();
 
-        case DHCPV6_DECLINE:
-            EXPECT_STREQ("DECLINE", Dhcpv6Srv::serverReceivedPacketName(type));
-            break;
+    // Check that the address we are about to use is indeed in pool
+    ASSERT_TRUE(subnet_->inPool(addr));
+
+    // 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 set and not using
+    // previous (500, 501, etc.) values
+    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);
 
-        case DHCPV6_INFORMATION_REQUEST:
-            EXPECT_STREQ("INFORMATION_REQUEST",
-                         Dhcpv6Srv::serverReceivedPacketName(type));
-            break;
+    OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+    ia->addOption(renewed_addr_opt);
+    req->addOption(ia);
+    req->addOption(clientid);
 
-        case DHCPV6_REBIND:
-            EXPECT_STREQ("REBIND", Dhcpv6Srv::serverReceivedPacketName(type));
-            break;
+    // Server-id is mandatory in RENEW
+    req->addOption(srv->getServerID());
 
-        case DHCPV6_RELEASE:
-            EXPECT_STREQ("RELEASE", Dhcpv6Srv::serverReceivedPacketName(type));
-            break;
+    // Pass it to the server and hope for a REPLY
+    Pkt6Ptr reply = srv->processRenew(req);
 
-        case DHCPV6_RENEW:
-            EXPECT_STREQ("RENEW", Dhcpv6Srv::serverReceivedPacketName(type));
-            break;
+    // Check if we get response at all
+    checkResponse(reply, DHCPV6_REPLY, 1234);
 
-        case DHCPV6_REQUEST:
-            EXPECT_STREQ("REQUEST", Dhcpv6Srv::serverReceivedPacketName(type));
-            break;
+    OptionPtr tmp = reply->getOption(D6O_IA_NA);
+    ASSERT_TRUE(tmp);
 
-        case DHCPV6_SOLICIT:
-            EXPECT_STREQ("SOLICIT", Dhcpv6Srv::serverReceivedPacketName(type));
-            break;
+    // 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());
 
-        default:
-            EXPECT_STREQ("UNKNOWN", Dhcpv6Srv::serverReceivedPacketName(type));
-        }
-    }
+    // 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);
+    ASSERT_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));
+    // equality or difference by 1 between cltt and expected is ok.
+    EXPECT_GE(1, abs(cltt - expected));
+
+    EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease6(addr_opt->getAddress()));
+}
+
+// This test verifies that incoming (invalid) RENEW can be handled properly.
+//
+// This test checks 3 scenarios:
+// 1. there is no such lease at all
+// 2. there is such a lease, but it is assigned to a different IAID
+// 3. there is such a lease, but it belongs to a different client
+//
+// 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, RenewReject) {
+
+    boost::scoped_ptr<NakedDhcpv6Srv> srv;
+    ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+
+    const IOAddress addr("2001:db8:1:1::dead");
+    const uint32_t transid = 1234;
+    const uint32_t valid_iaid = 234;
+    const uint32_t bogus_iaid = 456;
+
+    // Quick sanity check that the address we're about to use is ok
+    ASSERT_TRUE(subnet_->inPool(addr));
+
+    // GenerateClientId() also sets duid_
+    OptionPtr clientid = generateClientId();
+
+    // Check that the lease is NOT in the database
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+    ASSERT_FALSE(l);
+
+    // Let's create a RENEW
+    Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, transid));
+    req->setRemoteAddr(IOAddress("fe80::abcd"));
+    boost::shared_ptr<Option6IA> ia = generateIA(bogus_iaid, 1500, 3000);
+
+    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());
+
+    // Case 1: No lease known to server
+
+    // 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, transid);
+    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);
+    checkRejectedIA_NA(ia, STATUS_NoAddrsAvail);
+
+    // Check that there is no lease added
+    l = LeaseMgrFactory::instance().getLease6(addr);
+    ASSERT_FALSE(l);
+
+    // CASE 2: Lease is known and belongs to this client, but to a different IAID
+
+    // 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_, valid_iaid,
+                               501, 502, 503, 504, subnet_->getID(), 0));
+    lease->cltt_ = 123; // Let's use it as an indicator that the lease
+                        // was NOT updated.
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // Pass it to the server and hope for a REPLY
+    reply = srv->processRenew(req);
+    checkResponse(reply, DHCPV6_REPLY, transid);
+    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);
+    checkRejectedIA_NA(ia, STATUS_NoAddrsAvail);
+
+    // There is a iaid mis-match, so server should respond that there is
+    // no such address to renew.
+
+    // CASE 3: Lease belongs to a client with different client-id
+    req->delOption(D6O_CLIENTID);
+    ia = boost::dynamic_pointer_cast<Option6IA>(req->getOption(D6O_IA_NA));
+    ia->setIAID(valid_iaid); // Now iaid in renew matches that in leasemgr
+    req->addOption(generateClientId(13)); // generate different DUID
+                                          // (with length 13)
+
+    reply = srv->processRenew(req);
+    checkResponse(reply, DHCPV6_REPLY, transid);
+    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);
+    checkRejectedIA_NA(ia, STATUS_NoAddrsAvail);
+
+    lease = LeaseMgrFactory::instance().getLease6(addr);
+    ASSERT_TRUE(lease);
+    // Verify that the lease was not updated.
+    EXPECT_EQ(123, lease->cltt_);
+
+    EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease6(addr));
 }
 
 // This test verifies if the status code option is generated properly.
@@ -802,7 +996,7 @@ TEST_F(Dhcpv6SrvTest, StatusCode) {
 
     // a dummy content for client-id
     uint8_t expected[] = {
-        0x0, 0xD, // option code = 14
+        0x0, 0xD, // option code = 13
         0x0, 0x7, // option length = 7
         0x0, 0x3, // status code = 3
         0x41, 0x42, 0x43, 0x44, 0x45 // string value ABCDE
@@ -820,8 +1014,8 @@ TEST_F(Dhcpv6SrvTest, StatusCode) {
     EXPECT_EQ(0, memcmp(expected, buf.getData(), sizeof(expected)));
 }
 
-// This test verifies if the selectSubnet() method works as expected.
-TEST_F(Dhcpv6SrvTest, SelectSubnet) {
+// This test verifies if the sanityCheck() really checks options presence.
+TEST_F(Dhcpv6SrvTest, sanityCheck) {
     boost::scoped_ptr<NakedDhcpv6Srv> srv;
     ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
 
@@ -829,18 +1023,63 @@ TEST_F(Dhcpv6SrvTest, SelectSubnet) {
 
     // check that the packets originating from local addresses can be
     pkt->setRemoteAddr(IOAddress("fe80::abcd"));
-    EXPECT_EQ(subnet_, srv->selectSubnet(pkt));
 
-    // packets originating from subnet A will select subnet A
-    pkt->setRemoteAddr(IOAddress("2001:db8:1::6789"));
-    EXPECT_EQ(subnet_, srv->selectSubnet(pkt));
+    // 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
+    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN),
+                 RFCViolation);
+
+    // This doesn't make much sense, but let's check it for completeness
+    EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::FORBIDDEN, Dhcpv6Srv::FORBIDDEN));
+
+    OptionPtr clientid = generateClientId();
+    pkt->addOption(clientid);
+
+    // client-id is mandatory, server-id is forbidden (as in SOLICIT or REBIND)
+    EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN));
+
+    pkt->addOption(srv->getServerID());
+
+    // both client-id and server-id are mandatory (as in REQUEST, RENEW, RELEASE, DECLINE)
+    EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY));
+
+    // sane section ends here, let's do some negative tests as well
+
+    pkt->addOption(clientid);
+    pkt->addOption(clientid);
+
+    // with more than one client-id it should throw, no matter what
+    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
+                 RFCViolation);
+    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
+                 RFCViolation);
+    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
+                 RFCViolation);
+    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
+                 RFCViolation);
+
+    pkt->delOption(D6O_CLIENTID);
+    pkt->delOption(D6O_CLIENTID);
+
+    // again we have only one client-id
+
+    // let's try different type of insanity - several server-ids
+    pkt->addOption(srv->getServerID());
+    pkt->addOption(srv->getServerID());
+
+    // with more than one server-id it should throw, no matter what
+    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
+                 RFCViolation);
+    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
+                 RFCViolation);
+    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
+                 RFCViolation);
+    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
+                 RFCViolation);
 
-    // packets from a subnet that is not supported will not get
-    // a subnet
-    pkt->setRemoteAddr(IOAddress("3000::faf"));
-    EXPECT_FALSE(srv->selectSubnet(pkt));
 
-    /// @todo: expand this test once support for relays is implemented
 }
 
 }   // end of anonymous namespace
diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc
index 4638025..2a53f0f 100644
--- a/src/lib/dhcp/option.cc
+++ b/src/lib/dhcp/option.cc
@@ -314,6 +314,10 @@ void Option::setData(const OptionBufferConstIter first,
     std::copy(first, last, data_.begin());
 }
 
+bool Option::equal(const OptionPtr& other) const {
+    return ( (getType() == other->getType()) &&
+             (getData() == other->getData()) );
+}
 
 Option::~Option() {
 
diff --git a/src/lib/dhcp/option.h b/src/lib/dhcp/option.h
index a6b0622..29f0f01 100644
--- a/src/lib/dhcp/option.h
+++ b/src/lib/dhcp/option.h
@@ -197,7 +197,7 @@ public:
     /// Returns option type (0-255 for DHCPv4, 0-65535 for DHCPv6)
     ///
     /// @return option type
-    uint16_t getType() { return (type_); }
+    uint16_t getType() const { return (type_); }
 
     /// Returns length of the complete option (data length + DHCPv4/DHCPv6
     /// option header)
@@ -219,7 +219,7 @@ public:
     ///
     /// @return pointer to actual data (or reference to an empty vector
     ///         if there is no data)
-    virtual const OptionBuffer& getData() { return (data_); }
+    virtual const OptionBuffer& getData() const { return (data_); }
 
     /// Adds a sub-option.
     ///
@@ -303,6 +303,19 @@ public:
     /// just to force that every option has virtual dtor
     virtual ~Option();
 
+    /// @brief Checks if two options are equal
+    ///
+    /// Equality verifies option type and option content. Care should
+    /// be taken when using this method. Implementation for derived
+    /// classes should be provided when this method is expected to be
+    /// used. It is safe in general, as the first check (different types)
+    /// will detect differences between base Option and derived
+    /// objects.
+    ///
+    /// @param other the other option
+    /// @return true if both options are equal
+    virtual bool equal(const OptionPtr& other) const;
+
 protected:
     /// Builds raw (over-wire) buffer of this option, including all
     /// defined suboptions. Version for building DHCPv4 options.
diff --git a/src/lib/dhcp/option6_ia.h b/src/lib/dhcp/option6_ia.h
index 54624d0..32f3667 100644
--- a/src/lib/dhcp/option6_ia.h
+++ b/src/lib/dhcp/option6_ia.h
@@ -68,12 +68,17 @@ public:
     /// Sets T1 timer.
     ///
     /// @param t1 t1 value to be set
-    void setT1(uint32_t t1) { t1_=t1; }
+    void setT1(uint32_t t1) { t1_ = t1; }
 
     /// Sets T2 timer.
     ///
     /// @param t2 t2 value to be set
-    void setT2(uint32_t t2) { t2_=t2; }
+    void setT2(uint32_t t2) { t2_ = t2; }
+
+    /// Sets Identity Association Identifier.
+    ///
+    /// @param iaid IAID value to be set
+    void setIAID(uint32_t iaid) { iaid_ = iaid; }
 
     /// Returns IA identifier.
     ///
diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc
index 12597c3..2c97b07 100644
--- a/src/lib/dhcp/pkt6.cc
+++ b/src/lib/dhcp/pkt6.cc
@@ -81,13 +81,6 @@ Pkt6::pack() {
 
 bool
 Pkt6::packUDP() {
-
-    // TODO: Once OutputBuffer is used here, some thing like this
-    // will be used. Yikes! That's ugly.
-    // bufferOut_.writeData(ciaddr_.getAddress().to_v6().to_bytes().data(), 16);
-    // It is better to implement a method in IOAddress that extracts
-    // vector<uint8_t>
-
     try {
         // DHCPv6 header: message-type (1 octect) + transaction id (3 octets)
         bufferOut_.writeUint8(msg_type_);
@@ -172,17 +165,30 @@ Pkt6::toText() {
     return tmp.str();
 }
 
-boost::shared_ptr<isc::dhcp::Option>
+OptionPtr
 Pkt6::getOption(uint16_t opt_type) {
     isc::dhcp::Option::OptionCollection::const_iterator x = options_.find(opt_type);
     if (x!=options_.end()) {
         return (*x).second;
     }
-    return boost::shared_ptr<isc::dhcp::Option>(); // NULL
+    return OptionPtr(); // NULL
+}
+
+isc::dhcp::Option::OptionCollection
+Pkt6::getOptions(uint16_t opt_type) {
+    isc::dhcp::Option::OptionCollection found;
+
+    for (Option::OptionCollection::const_iterator x = options_.begin();
+         x != options_.end(); ++x) {
+        if (x->first == opt_type) {
+            found.insert(make_pair(opt_type, x->second));
+        }
+    }
+    return (found);
 }
 
 void
-Pkt6::addOption(boost::shared_ptr<Option> opt) {
+Pkt6::addOption(const OptionPtr& opt) {
     options_.insert(pair<int, boost::shared_ptr<Option> >(opt->getType(), opt));
 }
 
@@ -205,6 +211,52 @@ Pkt6::updateTimestamp() {
     timestamp_ = boost::posix_time::microsec_clock::universal_time();
 }
 
+const char*
+Pkt6::getName(uint8_t type) {
+    static const char* CONFIRM = "CONFIRM";
+    static const char* DECLINE = "DECLINE";
+    static const char* INFORMATION_REQUEST = "INFORMATION_REQUEST";
+    static const char* REBIND = "REBIND";
+    static const char* RELEASE = "RELEASE";
+    static const char* RENEW = "RENEW";
+    static const char* REQUEST = "REQUEST";
+    static const char* SOLICIT = "SOLICIT";
+    static const char* UNKNOWN = "UNKNOWN";
+
+    switch (type) {
+    case DHCPV6_CONFIRM:
+        return (CONFIRM);
+
+    case DHCPV6_DECLINE:
+        return (DECLINE);
+
+    case DHCPV6_INFORMATION_REQUEST:
+        return (INFORMATION_REQUEST);
+
+    case DHCPV6_REBIND:
+        return (REBIND);
+
+    case DHCPV6_RELEASE:
+        return (RELEASE);
+
+    case DHCPV6_RENEW:
+        return (RENEW);
+
+    case DHCPV6_REQUEST:
+        return (REQUEST);
+
+    case DHCPV6_SOLICIT:
+        return (SOLICIT);
+
+    default:
+        ;
+    }
+    return (UNKNOWN);
+}
+
+const char* Pkt6::getName() const {
+    return (getName(getType()));
+}
 
 } // end of isc::dhcp namespace
 } // end of isc namespace
diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h
index 5782737..0542df9 100644
--- a/src/lib/dhcp/pkt6.h
+++ b/src/lib/dhcp/pkt6.h
@@ -127,7 +127,7 @@ public:
     /// Returns message type (e.g. 1 = SOLICIT)
     ///
     /// @return message type
-    uint8_t getType() { return (msg_type_); }
+    uint8_t getType() const { return (msg_type_); }
 
     /// Sets message type (e.g. 1 = SOLICIT)
     ///
@@ -147,19 +147,28 @@ public:
     /// Adds an option to this packet.
     ///
     /// @param opt option to be added.
-    void addOption(OptionPtr opt);
+    void addOption(const OptionPtr& opt);
 
     /// @brief Returns the first option of specified type.
     ///
     /// Returns the first option of specified type. Note that in DHCPv6 several
     /// instances of the same option are allowed (and frequently used).
-    /// See getOptions().
+    /// Also see \ref getOptions().
     ///
     /// @param type option type we are looking for
     ///
     /// @return pointer to found option (or NULL)
     OptionPtr getOption(uint16_t type);
 
+    /// @brief Returns all instances of specified type.
+    ///
+    /// Returns all instances of options of the specified type. DHCPv6 protocol
+    /// allows (and uses frequently) multiple instances.
+    ///
+    /// @param type option type we are looking for
+    /// @return instance of option collection with requested options
+    isc::dhcp::Option::OptionCollection getOptions(uint16_t type);
+
     /// Attempts to delete first suboption of requested type
     ///
     /// @param type Type of option to be deleted.
@@ -246,8 +255,6 @@ public:
     /// @return interface name
     void setIface(const std::string& iface ) { iface_ = iface; };
 
-    /// TODO Need to implement getOptions() as well
-
     /// collection of options present in this message
     ///
     /// @warning This protected member is accessed by derived
@@ -266,6 +273,34 @@ public:
     /// @throw isc::Unexpected if timestamp update failed
     void updateTimestamp();
 
+    /// @brief Return textual type of packet.
+    ///
+    /// Returns the name of valid packet received by the server (e.g. SOLICIT).
+    /// If the packet is unknown - or if it is a valid DHCP packet but not one
+    /// expected to be received by the server (such as an ADVERTISE), the string
+    /// "UNKNOWN" is returned.  This method is used in debug messages.
+    ///
+    /// As the operation of the method does not depend on any server state, it
+    /// is declared static. There is also non-static getName() method that
+    /// works on Pkt6 objects.
+    ///
+    /// @param type DHCPv6 packet type
+    ///
+    /// @return Pointer to "const" string containing the packet name.
+    ///         Note that this string is statically allocated and MUST NOT
+    ///         be freed by the caller.
+    static const char* getName(uint8_t type);
+
+    /// @brief returns textual representation of packet type.
+    ///
+    /// This method requires an object. There is also static version, which
+    /// requires one parameter (type).
+    ///
+    /// @return Pointer to "const" string containing packet name.
+    ///         Note that this string is statically allocated and MUST NOT
+    ///         be freed by the caller.
+    const char* getName() const;
+
 protected:
     /// Builds on wire packet for TCP transmission.
     ///
diff --git a/src/lib/dhcp/tests/option6_ia_unittest.cc b/src/lib/dhcp/tests/option6_ia_unittest.cc
index 1ee618a..24f101d 100644
--- a/src/lib/dhcp/tests/option6_ia_unittest.cc
+++ b/src/lib/dhcp/tests/option6_ia_unittest.cc
@@ -109,7 +109,13 @@ TEST_F(Option6IATest, basic) {
 }
 
 TEST_F(Option6IATest, simple) {
-    Option6IA * ia = new Option6IA(D6O_IA_NA, 1234);
+    Option6IA* ia = new Option6IA(D6O_IA_NA, 1234);
+
+    // Check that the values are really different than what we are about
+    // to set them to.
+    EXPECT_NE(2345, ia->getT1());
+    EXPECT_NE(3456, ia->getT2());
+
     ia->setT1(2345);
     ia->setT2(3456);
 
@@ -119,6 +125,9 @@ TEST_F(Option6IATest, simple) {
     EXPECT_EQ(2345, ia->getT1());
     EXPECT_EQ(3456, ia->getT2());
 
+    ia->setIAID(890);
+    EXPECT_EQ(890, ia->getIAID());
+
     EXPECT_NO_THROW(
         delete ia;
     );
diff --git a/src/lib/dhcp/tests/option_unittest.cc b/src/lib/dhcp/tests/option_unittest.cc
index 50d143f..afa64d5 100644
--- a/src/lib/dhcp/tests/option_unittest.cc
+++ b/src/lib/dhcp/tests/option_unittest.cc
@@ -524,4 +524,27 @@ TEST_F(OptionTest, setData) {
     EXPECT_TRUE(0 == memcmp(&buf_[0], test_data + opt1->getHeaderLen(),
                             buf_.size()));
 }
+
+// This test verifies that options can be compared using equal() method.
+TEST_F(OptionTest, equal) {
+
+    // five options with varying lengths
+    OptionPtr opt1(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 1));
+    OptionPtr opt2(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
+    OptionPtr opt3(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3));
+
+    // the same content as opt2, but different type
+    OptionPtr opt4(new Option(Option::V6, 1, buf_.begin(), buf_.begin() + 2));
+
+    // another instance with the same type and content as opt2
+    OptionPtr opt5(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
+
+    EXPECT_TRUE(opt1->equal(opt1));
+
+    EXPECT_FALSE(opt1->equal(opt2));
+    EXPECT_FALSE(opt1->equal(opt3));
+    EXPECT_FALSE(opt1->equal(opt4));
+
+    EXPECT_TRUE(opt2->equal(opt5));
+}
 }
diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc
index b2dd16a..cdaad3b 100644
--- a/src/lib/dhcp/tests/pkt6_unittest.cc
+++ b/src/lib/dhcp/tests/pkt6_unittest.cc
@@ -170,6 +170,8 @@ TEST_F(Pkt6Test, packUnpack) {
     delete clone;
 }
 
+// This test verifies that options can be added (addOption()), retrieved
+// (getOption(), getOptions()) and deleted (delOption()).
 TEST_F(Pkt6Test, addGetDelOptions) {
     Pkt6* parent = new Pkt6(DHCPV6_SOLICIT, random() );
 
@@ -190,6 +192,25 @@ TEST_F(Pkt6Test, addGetDelOptions) {
     // now there are 2 options of type 2
     parent->addOption(opt3);
 
+    Option::OptionCollection options = parent->getOptions(2);
+    EXPECT_EQ(2, options.size()); // there should be 2 instances
+
+    // both options must be of type 2 and there must not be
+    // any other type returned
+    for (Option::OptionCollection::const_iterator x= options.begin();
+         x != options.end(); ++x) {
+        EXPECT_EQ(2, x->second->getType());
+    }
+
+    // Try to get a single option. Normally for singular options
+    // it is better to use getOption(), but getOptions() must work
+    // as well
+    options = parent->getOptions(1);
+    ASSERT_EQ(1, options.size());
+
+    EXPECT_EQ(1, (*options.begin()).second->getType());
+    EXPECT_EQ(opt1, options.begin()->second);
+
     // let's delete one of them
     EXPECT_EQ(true, parent->delOption(2));
 
@@ -205,6 +226,10 @@ TEST_F(Pkt6Test, addGetDelOptions) {
     // let's try to delete - should fail
     EXPECT_TRUE(false ==  parent->delOption(2));
 
+    // Finally try to get a non-existent option
+    options = parent->getOptions(1234);
+    EXPECT_EQ(0, options.size());
+
     delete parent;
 }
 
@@ -234,4 +259,52 @@ TEST_F(Pkt6Test, Timestamp) {
     EXPECT_TRUE(ts_period.length().total_microseconds() >= 0);
 }
 
+// This test verifies that getName() method returns proper
+// packet type names.
+TEST_F(Pkt6Test, getName) {
+    // Check all possible packet types
+    for (int itype = 0; itype < 256; ++itype) {
+        uint8_t type = itype;
+
+        switch (type) {
+        case DHCPV6_CONFIRM:
+            EXPECT_STREQ("CONFIRM", Pkt6::getName(type));
+            break;
+
+        case DHCPV6_DECLINE:
+            EXPECT_STREQ("DECLINE", Pkt6::getName(type));
+            break;
+
+        case DHCPV6_INFORMATION_REQUEST:
+            EXPECT_STREQ("INFORMATION_REQUEST",
+                         Pkt6::getName(type));
+            break;
+
+        case DHCPV6_REBIND:
+            EXPECT_STREQ("REBIND", Pkt6::getName(type));
+            break;
+
+        case DHCPV6_RELEASE:
+            EXPECT_STREQ("RELEASE", Pkt6::getName(type));
+            break;
+
+        case DHCPV6_RENEW:
+            EXPECT_STREQ("RENEW", Pkt6::getName(type));
+            break;
+
+        case DHCPV6_REQUEST:
+            EXPECT_STREQ("REQUEST", Pkt6::getName(type));
+            break;
+
+        case DHCPV6_SOLICIT:
+            EXPECT_STREQ("SOLICIT", Pkt6::getName(type));
+            break;
+
+        default:
+            EXPECT_STREQ("UNKNOWN", Pkt6::getName(type));
+        }
+    }
+}
+
+
 }
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index aa1ef1f..c7d7ac7 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -172,7 +172,7 @@ public:
                     // Use option type as the index key. The type is held
                     // in OptionPtr object so we have to call Option::getType
                     // to retrieve this key for each element.
-                    boost::multi_index::mem_fun<
+                    boost::multi_index::const_mem_fun<
                         Option,
                         uint16_t,
                         &Option::getType



More information about the bind10-changes mailing list