Browse Source

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

Conflicts:
	ChangeLog
Tomek Mrugalski 12 years ago
parent
commit
92758ecffc

+ 5 - 0
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

+ 27 - 0
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.

+ 213 - 84
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);
-}
-
 };
 };

+ 61 - 19
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

+ 286 - 47
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

+ 4 - 0
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() {
 

+ 15 - 2
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.

+ 7 - 2
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.
     ///

+ 62 - 10
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

+ 40 - 5
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.
     ///

+ 10 - 1
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;
     );

+ 23 - 0
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));
+}
 }

+ 73 - 0
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));
+        }
+    }
+}
+
+
 }

+ 1 - 1
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