Browse Source

Merge branch 'master' of ssh://git.bind10.isc.org/var/bind10/git/bind10

Conflicts:
	ChangeLog
Kean Johnston 11 years ago
parent
commit
ccb253d801

+ 7 - 2
ChangeLog

@@ -1,9 +1,14 @@
-702.    [bug]		kean
+703.    [bug]		kean
 	A bug in b10-msgq was fixed where it would remove the socket file if
 	there was an existing copy of b10-msgq running. It now correctly
 	detects and reports this without removing the socket file.
 	(Trac #433, git c18a49b0435c656669e6f87ef65d44dc98e0e726)
 
+702.	[func]		marcin
+	perfdhcp: support for sending DHCPv6 Renew messages at the specified
+	rate and measure performance.
+	(Trac #3183, git 66f2939830926f4337623b159210103b5a8e2434)
+
 701.	[bug]		tomek
 	libdhcp++: Incoming DHCPv6 IAPREFIX option is now parsed properly.
 	(Trac #3211, git ed43618a2c7b2387d76f99a5a4b1a3e05ac70f5e)
@@ -69,7 +74,7 @@
 	libdhcp++: Created definitions for standard DHCPv4 options:
 	tftp-server-name (66) and boot-file-name (67). Also, fixed definition
 	of DHCPv4 option time-offset (2).
-	(Trac #3199, git abcd)
+	(Trac #3199, git 6e171110c4dd9ae3b1be828b9516efc65c33460b)
 
 690.	[bug]		tomek
 	b10-dhcp4: Relay Agent Info option is now echoed back in

+ 1 - 0
tests/tools/perfdhcp/Makefile.am

@@ -23,6 +23,7 @@ perfdhcp_SOURCES += command_options.cc command_options.h
 perfdhcp_SOURCES += localized_option.h
 perfdhcp_SOURCES += perf_pkt6.cc perf_pkt6.h
 perfdhcp_SOURCES += perf_pkt4.cc perf_pkt4.h
+perfdhcp_SOURCES += packet_storage.h
 perfdhcp_SOURCES += pkt_transform.cc pkt_transform.h
 perfdhcp_SOURCES += stats_mgr.h
 perfdhcp_SOURCES += test_control.cc test_control.h

+ 61 - 33
tests/tools/perfdhcp/command_options.cc

@@ -12,19 +12,21 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include "command_options.h"
+#include <exceptions/exceptions.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/duid.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
 #include <config.h>
+#include <sstream>
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdint.h>
 #include <unistd.h>
 
-#include <boost/lexical_cast.hpp>
-#include <boost/date_time/posix_time/posix_time.hpp>
-
-#include <exceptions/exceptions.h>
-#include <dhcp/iface_mgr.h>
-#include <dhcp/duid.h>
-#include "command_options.h"
 
 using namespace std;
 using namespace isc;
@@ -111,6 +113,7 @@ CommandOptions::reset() {
     exchange_mode_ = DORA_SARR;
     lease_type_.set(LeaseType::ADDRESS);
     rate_ = 0;
+    renew_rate_ = 0;
     report_delay_ = 0;
     clients_num_ = 0;
     mac_template_.assign(mac, mac + 6);
@@ -208,7 +211,7 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
     // In this section we collect argument values from command line
     // they will be tuned and validated elsewhere
     while((opt = getopt(argc, argv, "hv46r:t:R:b:n:p:d:D:l:P:a:L:"
-                        "s:iBc1T:X:O:E:S:I:x:w:e:")) != -1) {
+                        "s:iBc1T:X:O:E:S:I:x:w:e:f:")) != -1) {
         stream << " -" << static_cast<char>(opt);
         if (optarg) {
             stream << " " << optarg;
@@ -299,6 +302,11 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
                                              " must not be a negative integer");
             break;
 
+        case 'f':
+            renew_rate_ = positiveInteger("value of the renew rate: -f<renew-rate>"
+                                          " must be a positive integer");
+            break;
+
         case 'h':
             usage();
             return (true);
@@ -680,6 +688,8 @@ CommandOptions::validate() const {
           "-B is not compatible with IPv6 (-6)");
     check((getIpVersion() != 6) && (isRapidCommit() != 0),
           "-6 (IPv6) must be set to use -c");
+    check((getIpVersion() != 6) && (getRenewRate() !=0),
+          "-f<renew-rate> may be used with -6 (IPv6) only");
     check((getExchangeMode() == DO_SA) && (getNumRequests().size() > 1),
           "second -n<num-request> is not compatible with -i");
     check((getIpVersion() == 4) && !getLeaseType().is(LeaseType::ADDRESS),
@@ -692,44 +702,52 @@ CommandOptions::validate() const {
           "second -d<drop-time> is not compatible with -i");
     check((getExchangeMode() == DO_SA) &&
           ((getMaxDrop().size() > 1) || (getMaxDropPercentage().size() > 1)),
-          "second -D<max-drop> is not compatible with -i\n");
+          "second -D<max-drop> is not compatible with -i");
     check((getExchangeMode() == DO_SA) && (isUseFirst()),
-          "-1 is not compatible with -i\n");
+          "-1 is not compatible with -i");
     check((getExchangeMode() == DO_SA) && (getTemplateFiles().size() > 1),
-          "second -T<template-file> is not compatible with -i\n");
+          "second -T<template-file> is not compatible with -i");
     check((getExchangeMode() == DO_SA) && (getTransactionIdOffset().size() > 1),
-          "second -X<xid-offset> is not compatible with -i\n");
+          "second -X<xid-offset> is not compatible with -i");
     check((getExchangeMode() == DO_SA) && (getRandomOffset().size() > 1),
-          "second -O<random-offset is not compatible with -i\n");
+          "second -O<random-offset is not compatible with -i");
     check((getExchangeMode() == DO_SA) && (getElapsedTimeOffset() >= 0),
-          "-E<time-offset> is not compatible with -i\n");
+          "-E<time-offset> is not compatible with -i");
     check((getExchangeMode() == DO_SA) && (getServerIdOffset() >= 0),
-          "-S<srvid-offset> is not compatible with -i\n");
+          "-S<srvid-offset> is not compatible with -i");
     check((getExchangeMode() == DO_SA) && (getRequestedIpOffset() >= 0),
-          "-I<ip-offset> is not compatible with -i\n");
+          "-I<ip-offset> is not compatible with -i");
+    check((getExchangeMode() == DO_SA) && (getRenewRate() != 0),
+          "-f<renew-rate> is not compatible with -i");
     check((getExchangeMode() != DO_SA) && (isRapidCommit() != 0),
-          "-i must be set to use -c\n");
+          "-i must be set to use -c");
     check((getRate() == 0) && (getReportDelay() != 0),
-          "-r<rate> must be set to use -t<report>\n");
+          "-r<rate> must be set to use -t<report>");
     check((getRate() == 0) && (getNumRequests().size() > 0),
-          "-r<rate> must be set to use -n<num-request>\n");
+          "-r<rate> must be set to use -n<num-request>");
     check((getRate() == 0) && (getPeriod() != 0),
-          "-r<rate> must be set to use -p<test-period>\n");
+          "-r<rate> must be set to use -p<test-period>");
     check((getRate() == 0) &&
           ((getMaxDrop().size() > 0) || getMaxDropPercentage().size() > 0),
-          "-r<rate> must be set to use -D<max-drop>\n");
+          "-r<rate> must be set to use -D<max-drop>");
+    check((getRate() != 0) && (getRenewRate() > getRate()),
+          "Renew rate specified as -f<renew-rate> must not be greater than"
+          " the rate specified as -r<rate>");
+    check((getRate() == 0) && (getRenewRate() != 0),
+          "Renew rate specified as -f<renew-rate> must not be specified"
+          " when -r<rate> parameter is not specified");
     check((getTemplateFiles().size() < getTransactionIdOffset().size()),
-          "-T<template-file> must be set to use -X<xid-offset>\n");
+          "-T<template-file> must be set to use -X<xid-offset>");
     check((getTemplateFiles().size() < getRandomOffset().size()),
-          "-T<template-file> must be set to use -O<random-offset>\n");
+          "-T<template-file> must be set to use -O<random-offset>");
     check((getTemplateFiles().size() < 2) && (getElapsedTimeOffset() >= 0),
-          "second/request -T<template-file> must be set to use -E<time-offset>\n");
+          "second/request -T<template-file> must be set to use -E<time-offset>");
     check((getTemplateFiles().size() < 2) && (getServerIdOffset() >= 0),
           "second/request -T<template-file> must be set to "
-          "use -S<srvid-offset>\n");
+          "use -S<srvid-offset>");
     check((getTemplateFiles().size() < 2) && (getRequestedIpOffset() >= 0),
           "second/request -T<template-file> must be set to "
-          "use -I<ip-offset>\n");
+          "use -I<ip-offset>");
 
 }
 
@@ -737,6 +755,8 @@ void
 CommandOptions::check(bool condition, const std::string& errmsg) const {
     // The same could have been done with macro or just if statement but
     // we prefer functions to macros here
+    std::ostringstream stream;
+    stream << errmsg << "\n";
     if (condition) {
         isc_throw(isc::InvalidParameter, errmsg);
     }
@@ -793,6 +813,9 @@ CommandOptions::printCommandLine() const {
     if (rate_ != 0) {
         std::cout << "rate[1/s]=" << rate_ <<  std::endl;
     }
+    if (getRenewRate() != 0) {
+        std::cout << "renew-rate[1/s]=" << getRenewRate() << std::endl;
+    }
     if (report_delay_ != 0) {
         std::cout << "report[s]=" << report_delay_ << std::endl;
     }
@@ -875,13 +898,14 @@ CommandOptions::printCommandLine() const {
 void
 CommandOptions::usage() const {
     std::cout <<
-        "perfdhcp [-hv] [-4|-6] [-e<lease-type>] [-r<rate>] [-t<report>]\n"
-        "    [-R<range>] [-b<base>] [-n<num-request>] [-p<test-period>]\n"
-        "    [-d<drop-time>] [-D<max-drop>] [-l<local-addr|interface>]\n"
-        "    [-P<preload>] [-a<aggressivity>] [-L<local-port>] [-s<seed>]\n"
-        "    [-i] [-B] [-c] [-1] [-T<template-file>] [-X<xid-offset>]\n"
-        "    [-O<random-offset] [-E<time-offset>] [-S<srvid-offset>]\n"
-        "    [-I<ip-offset>] [-x<diagnostic-selector>] [-w<wrapped>] [server]\n"
+        "perfdhcp [-hv] [-4|-6] [-e<lease-type>] [-r<rate>] [-f<renew-rate>]\n"
+        "         [-t<report>] [-R<range>] [-b<base>] [-n<num-request>]\n"
+        "         [-p<test-period>] [-d<drop-time>] [-D<max-drop>]\n"
+        "         [-l<local-addr|interface>] [-P<preload>] [-a<aggressivity>]\n"
+        "         [-L<local-port>] [-s<seed>] [-i] [-B] [-c] [-1]\n"
+        "         [-T<template-file>] [-X<xid-offset>] [-O<random-offset]\n"
+        "         [-E<time-offset>] [-S<srvid-offset>] [-I<ip-offset>]\n"
+        "         [-x<diagnostic-selector>] [-w<wrapped>] [server]\n"
         "\n"
         "The [server] argument is the name/address of the DHCP server to\n"
         "contact.  For DHCPv4 operation, exchanges are initiated by\n"
@@ -924,6 +948,10 @@ CommandOptions::usage() const {
         "-E<time-offset>: Offset of the (DHCPv4) secs field / (DHCPv6)\n"
         "    elapsed-time option in the (second/request) template.\n"
         "    The value 0 disables it.\n"
+        "-f<renew-rate>: A rate at which IPv6 Renew requests are sent to\n"
+        "    a server. This value must not be equal or lower than the rate\n"
+        "    specified as -r<rate>. If -r<rate> is not specified, this\n"
+        "    parameter must not be specified too.\n"
         "-h: Print this help.\n"
         "-i: Do only the initial part of an exchange: DO or SA, depending on\n"
         "    whether -6 is given.\n"

+ 10 - 2
tests/tools/perfdhcp/command_options.h

@@ -16,11 +16,12 @@
 #ifndef COMMAND_OPTIONS_H
 #define COMMAND_OPTIONS_H
 
+#include <boost/noncopyable.hpp>
+
+#include <stdint.h>
 #include <string>
 #include <vector>
 
-#include <boost/noncopyable.hpp>
-
 namespace isc {
 namespace perfdhcp {
 
@@ -155,6 +156,11 @@ public:
     /// \return exchange rate per second.
     int getRate() const { return rate_; }
 
+    /// \brief Returns a rate at which IPv6 Renew messages are sent.
+    ///
+    /// \return A rate at which IPv6 Renew messages are sent.
+    int getRenewRate() const { return (renew_rate_); }
+
     /// \brief Returns delay between two performance reports.
     ///
     /// \return delay between two consecutive performance reports.
@@ -461,6 +467,8 @@ private:
     LeaseType lease_type_;
     /// Rate in exchange per second
     int rate_;
+    /// A rate at which DHCPv6 Renew messages are sent.
+    int renew_rate_;
     /// Delay between generation of two consecutive
     /// performance reports
     int report_delay_;

+ 161 - 0
tests/tools/perfdhcp/packet_storage.h

@@ -0,0 +1,161 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef PACKET_STORAGE_H
+#define PACKET_STORAGE_H
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <list>
+#include <stdint.h>
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief Represents a list of packets with a sequential and random access to
+/// list elements.
+///
+/// The main purpose of this class is to support sending Renew and Release
+/// messages from perfdhcp. The Renew and Release messages are sent for existing
+/// leases only. Therefore, the typical use case for this class is that it holds
+/// a list of Reply messages sent by the server in response to Request messages.
+/// The Request messages hold addresses and/or IPv6 prefixes acquired so they
+/// can be used to identify existing leases. When perfdhcp needs to send Renew
+/// or Release message, it will access one of the elements on this list and
+/// will create the Renew or Release message based on its content. Once the
+/// element (packet) is returned it is also deleted from the list, so as it is
+/// not used again. This class provide either sequential access to the packets
+/// or random access. The random access algorithm is much slower but at least
+/// it allows to simulate more real scenario when the renewing or releasing
+/// client is random.
+///
+/// \tparam Pkt4 or Pkt6 class, which represents DHCPv4 or DHCPv6 message
+/// respectively.
+///
+/// \note Although the class is intended to hold Pkt4 and Pkt6 objects, the
+/// current implementation is generic enough to holds any object wrapped in the
+/// boost::shared_ptr.
+template<typename T>
+class PacketStorage : public boost::noncopyable {
+public:
+    /// A type which represents the pointer to a packet.
+    typedef boost::shared_ptr<T> PacketPtr;
+
+private:
+    /// An internal container actually holding packets.
+    typedef typename std::list<PacketPtr> PacketContainer;
+    /// An iterator to the element in the internal container.
+    typedef typename PacketContainer::iterator PacketContainerIterator;
+
+public:
+
+    /// \brief Constructor.
+    PacketStorage() { }
+
+    /// \brief Appends the new packet object to the collection.
+    ///
+    /// \param packet A pointer to an object representing a packet.
+    void append(const PacketPtr& packet) {
+        storage_.push_back(packet);
+        if (storage_.size() == 1) {
+            current_pointer_ = storage_.begin();
+        }
+    }
+
+    /// \brief Removes packets from the storage.
+    ///
+    /// It is possible to specify a number of packets to be removed
+    /// from a storage. Packets are removed from the beginning of the
+    /// storage. If specified number is greater than the size of the
+    /// storage, all packets are removed.
+    ///
+    /// @param num A number of packets to be removed. If omitted,
+    /// all packets will be removed.
+    void clear(const uint64_t num = 0) {
+        if (num != 0) {
+            PacketContainerIterator last = storage_.begin();
+            std::advance(last, num > size() ? size() : num);
+            current_pointer_ = storage_.erase(storage_.begin(), last);
+        } else {
+            storage_.clear();
+            current_pointer_ = storage_.begin();
+        }
+    }
+
+    /// \brief Checks if the storage has no packets.
+    ///
+    /// \return true if storage is empty, false otherwise.
+    bool empty() const {
+        return (storage_.empty());
+    }
+
+    /// \brief Returns next packet from the storage.
+    ///
+    /// This function returns packets sequentially (in the same order
+    /// in which they have been appended). The returned packet is
+    /// instantly removed from the storage.
+    ///
+    /// \return next packet from the storage.
+    PacketPtr getNext() {
+        if (storage_.empty()) {
+            return (PacketPtr());
+        } else if (current_pointer_ == storage_.end()) {
+            current_pointer_ = storage_.begin();
+        }
+        PacketPtr packet = *current_pointer_;
+        current_pointer_ = storage_.erase(current_pointer_);
+        return (packet);
+    }
+
+    /// \brief Returns random packet from the storage.
+    ///
+    /// This function picks random packet from the storage and returns
+    /// it. It is way slower than the @c getNext function because it has to
+    /// iterate over all existing entries from the beginning of the storage
+    /// to the random packet's position. Therefore, care should be taken
+    /// when using this function to access elements when storage is large.
+    ///
+    /// \return random packet from the storage.
+    PacketPtr getRandom() {
+        if (empty()) {
+            return (PacketPtr());
+        }
+        current_pointer_ = storage_.begin();
+        if (size() > 1) {
+            std::advance(current_pointer_, rand() % (size() - 1));
+        }
+        PacketPtr packet = *current_pointer_;
+        current_pointer_ = storage_.erase(current_pointer_);
+        return (packet);
+    }
+
+    /// \brief Returns number of packets in the storage.
+    ///
+    /// \return number of packets in the storage.
+    uint64_t size() const {
+        return (storage_.size());
+    }
+
+private:
+
+    std::list<PacketPtr> storage_;            ///< Holds all appended packets.
+    PacketContainerIterator current_pointer_; ///< Holds the iterator to the
+                                              ///< next element returned.
+
+};
+
+} // namespace perfdhcp
+} // namespace isc
+
+#endif // PACKET_STORAGE_H

+ 10 - 4
tests/tools/perfdhcp/stats_mgr.h

@@ -15,8 +15,9 @@
 #ifndef STATS_MGR_H
 #define STATS_MGR_H
 
-#include <iostream>
-#include <map>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+#include <exceptions/exceptions.h>
 
 #include <boost/noncopyable.hpp>
 #include <boost/shared_ptr.hpp>
@@ -27,7 +28,9 @@
 #include <boost/multi_index/mem_fun.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
 
-#include <exceptions/exceptions.h>
+#include <iostream>
+#include <map>
+
 
 namespace isc {
 namespace perfdhcp {
@@ -120,7 +123,8 @@ public:
         XCHG_DO,  ///< DHCPv4 DISCOVER-OFFER
         XCHG_RA,  ///< DHCPv4 REQUEST-ACK
         XCHG_SA,  ///< DHCPv6 SOLICIT-ADVERTISE
-        XCHG_RR   ///< DHCPv6 REQUEST-REPLY
+        XCHG_RR,  ///< DHCPv6 REQUEST-REPLY
+        XCHG_RN   ///< DHCPv6 RENEW-REPLY
     };
 
     /// \brief Exchange Statistics.
@@ -1165,6 +1169,8 @@ public:
             return("SOLICIT-ADVERTISE");
         case XCHG_RR:
             return("REQUEST-REPLY");
+        case XCHG_RN:
+            return("RENEW-REPLY");
         default:
             return("Unknown exchange type");
         }

+ 153 - 16
tests/tools/perfdhcp/test_control.cc

@@ -98,6 +98,38 @@ TestControl::TestControl() {
 }
 
 void
+TestControl::cleanCachedPackets() {
+    CommandOptions& options = CommandOptions::instance();
+    // When Renews are not sent, Reply packets are not cached so there
+    // is nothing to do.
+    if (options.getRenewRate() == 0) {
+        return;
+    }
+
+    static boost::posix_time::ptime last_clean =
+        microsec_clock::universal_time();
+
+    // Check how much time has passed since last cleanup.
+    time_period time_since_clean(last_clean,
+                                 microsec_clock::universal_time());
+    // Cleanup every 1 second.
+    if (time_since_clean.length().total_seconds() >= 1) {
+        // Calculate how many cached packets to remove. Actually we could
+        // just leave enough packets to handle Renews for 1 second but
+        // since we want to randomize leases to be renewed so leave 5
+        // times more packets to randomize from.
+        // @todo The cache size might be controlled from the command line.
+        if (reply_storage_.size() > 5 * options.getRenewRate()) {
+            reply_storage_.clear(reply_storage_.size() -
+                                 5 * options.getRenewRate());
+        }
+        // Remember when we performed a cleanup for the last time.
+        // We want to do the next cleanup not earlier than in one second.
+        last_clean = microsec_clock::universal_time();
+    }
+}
+
+void
 TestControl::copyIaOptions(const Pkt6Ptr& pkt_from, Pkt6Ptr& pkt_to) {
     if (!pkt_from || !pkt_to) {
         isc_throw(BadValue, "NULL pointers must not be specified as arguments"
@@ -283,6 +315,31 @@ TestControl::checkExitConditions() const {
     return (false);
 }
 
+Pkt6Ptr
+TestControl::createRenew(const Pkt6Ptr& reply) {
+    if (!reply) {
+        isc_throw(isc::BadValue,"Unable to create Renew packet from the Reply packet"
+                  " because the instance of the Reply is NULL");
+    }
+    Pkt6Ptr renew(new Pkt6(DHCPV6_RENEW, generateTransid()));
+    // Client id.
+    OptionPtr opt_clientid = reply->getOption(D6O_CLIENTID);
+    if (!opt_clientid) {
+        isc_throw(isc::Unexpected, "failed to create Renew packet because client id"
+                  " option has not been found in the Reply from the server");
+    }
+    renew->addOption(opt_clientid);
+    // Server id.
+    OptionPtr opt_serverid = reply->getOption(D6O_SERVERID);
+    if (!opt_serverid) {
+        isc_throw(isc::Unexpected, "failed to create Renew packet because server id"
+                  " option has not been found in the Reply from the server");
+    }
+    renew->addOption(opt_serverid);
+    copyIaOptions(reply, renew);
+    return (renew);
+}
+
 OptionPtr
 TestControl::factoryElapsedTime6(Option::Universe, uint16_t,
                                  const OptionBuffer& buf) {
@@ -426,6 +483,24 @@ TestControl::generateDuid(uint8_t& randomized) const {
     return (duid);
 }
 
+uint32_t
+TestControl::getCurrentTimeout() const {
+    CommandOptions& options = CommandOptions::instance();
+    ptime now(microsec_clock::universal_time());
+    // Check that we haven't passed the moment to send the next set of
+    // packets.
+    if (now >= send_due_ ||
+        (options.getRenewRate() != 0 && now >= renew_due_)) {
+        return (0);
+    }
+
+    // There is a due time to send Solicit and Renew. We should adjust
+    // the timeout to the due time which occurs sooner.
+    ptime due = send_due_ > renew_due_ ? renew_due_ : send_due_;
+    time_period due_period(now, due);
+    return (due_period.length().total_microseconds());
+}
+
 int
 TestControl::getElapsedTimeOffset() const {
     int elp_offset = CommandOptions::instance().getIpVersion() == 4 ?
@@ -456,17 +531,18 @@ TestControl::getElapsedTime(const T& pkt1, const T& pkt2) {
 
 
 uint64_t
-TestControl::getNextExchangesNum() const {
+TestControl::getNextExchangesNum(const boost::posix_time::ptime& send_due,
+                                 const int rate) {
     CommandOptions& options = CommandOptions::instance();
     // Get current time.
     ptime now(microsec_clock::universal_time());
-    if (now >= send_due_) {
+    if (now >= send_due) {
         // Reset number of exchanges.
         uint64_t due_exchanges = 0;
         // If rate is specified from the command line we have to
         // synchornize with it.
-        if (options.getRate() != 0) {
-            time_period period(send_due_, now);
+        if (rate != 0) {
+            time_period period(send_due, now);
             time_duration duration = period.length();
             // due_factor indicates the number of seconds that
             // sending next chunk of packets will take.
@@ -475,7 +551,7 @@ TestControl::getNextExchangesNum() const {
             due_factor += duration.total_seconds();
             // Multiplying due_factor by expected rate gives the number
             // of exchanges to be initiated.
-            due_exchanges = static_cast<uint64_t>(due_factor * options.getRate());
+            due_exchanges = static_cast<uint64_t>(due_factor * rate);
             // We want to make sure that at least one packet goes out.
             if (due_exchanges == 0) {
                 due_exchanges = 1;
@@ -616,6 +692,9 @@ TestControl::initializeStatsMgr() {
             stats_mgr6_->addExchangeStats(StatsMgr6::XCHG_RR,
                                           options.getDropTime()[1]);
         }
+        if (options.getRenewRate() != 0) {
+            stats_mgr6_->addExchangeStats(StatsMgr6::XCHG_RN);
+        }
     }
     if (testDiags('i')) {
         if (options.getIpVersion() == 4) {
@@ -769,6 +848,17 @@ TestControl::sendPackets(const TestControlSocket& socket,
     }
 }
 
+uint64_t
+TestControl::sendRenewPackets(const TestControlSocket& socket,
+                              const uint64_t packets_num) {
+    for (uint64_t i = 0; i < packets_num; ++i) {
+        if (!sendRenew(socket)) {
+            return (i);
+        }
+    }
+    return (packets_num);
+}
+
 void
 TestControl::printDiagnostics() const {
     CommandOptions& options = CommandOptions::instance();
@@ -1024,20 +1114,27 @@ TestControl::processReceivedPacket6(const TestControlSocket& socket,
             }
         }
     } else if (packet_type == DHCPV6_REPLY) {
-        stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RR, pkt6);
+        Pkt6Ptr sent_packet = stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RR,
+                                                          pkt6);
+        if (sent_packet) {
+            if (CommandOptions::instance().getRenewRate() != 0) {
+                reply_storage_.append(pkt6);
+            }
+        } else {
+            stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RN, pkt6);
+        }
     }
 }
 
 uint64_t
 TestControl::receivePackets(const TestControlSocket& socket) {
-    int timeout = 0;
     bool receiving = true;
     uint64_t received = 0;
     while (receiving) {
         if (CommandOptions::instance().getIpVersion() == 4) {
             Pkt4Ptr pkt4;
             try {
-                pkt4 = IfaceMgr::instance().receive4(timeout);
+                pkt4 = IfaceMgr::instance().receive4(0, getCurrentTimeout());
             } catch (const Exception& e) {
                 std::cerr << "Failed to receive DHCPv4 packet: "
                           << e.what() <<  std::endl;
@@ -1055,7 +1152,7 @@ TestControl::receivePackets(const TestControlSocket& socket) {
         } else if (CommandOptions::instance().getIpVersion() == 6) {
             Pkt6Ptr pkt6;
             try {
-                pkt6 = IfaceMgr::instance().receive6(timeout);
+                pkt6 = IfaceMgr::instance().receive6(0, getCurrentTimeout());
             } catch (const Exception& e) {
                 std::cerr << "Failed to receive DHCPv6 packet: "
                           << e.what() << std::endl;
@@ -1156,6 +1253,8 @@ TestControl::reset() {
     send_due_ = microsec_clock::universal_time();
     last_sent_ = send_due_;
     last_report_ = send_due_;
+    renew_due_ = send_due_;
+    last_renew_ = send_due_;
     transid_gen_.reset();
     // Actual generators will have to be set later on because we need to
     // get command line parameters first.
@@ -1223,10 +1322,10 @@ TestControl::run() {
     initializeStatsMgr();
     for (;;) {
         // Calculate send due based on when last exchange was initiated.
-        updateSendDue();
+        updateSendDue(last_sent_, options.getRate(), send_due_);
         // Calculate number of packets to be sent to stay
         // catch up with rate.
-        uint64_t packets_due = getNextExchangesNum();
+        uint64_t packets_due = getNextExchangesNum(send_due_, options.getRate());
         if ((packets_due == 0) && testDiags('i')) {
             if (options.getIpVersion() == 4) {
                 stats_mgr4_->incrementCounter("shortwait");
@@ -1249,11 +1348,29 @@ TestControl::run() {
         // Initiate new DHCP packet exchanges.
         sendPackets(socket, packets_due);
 
+        // If -f<renew-rate> option was specified we have to check how many
+        // Renew packets should be sent to catch up with a desired rate.
+        if ((options.getIpVersion() == 6) && (options.getRenewRate() != 0)) {
+            updateSendDue(last_renew_, options.getRenewRate(), renew_due_);
+            uint64_t renew_packets_due =
+                getNextExchangesNum(renew_due_, options.getRenewRate());
+            // Send renew packets.
+            sendRenewPackets(socket, renew_packets_due);
+        }
+
         // Report delay means that user requested printing number
         // of sent/received/dropped packets repeatedly.
         if (options.getReportDelay() > 0) {
             printIntermediateStats();
         }
+
+        // If we are sending Renews to the server, the Reply packets are cached
+        // so as leases for which we send Renews can be idenitfied. The major
+        // issue with this approach is that most of the time we are caching
+        // more packets than we actually need. This function removes excessive
+        // Reply messages to reduce the memory and CPU utilization. Note that
+        // searches in the long list of Reply packets increases CPU utilization.
+        cleanCachedPackets();
     }
     printStats();
 
@@ -1429,6 +1546,25 @@ TestControl::sendDiscover4(const TestControlSocket& socket,
     saveFirstPacket(pkt4);
 }
 
+bool
+TestControl::sendRenew(const TestControlSocket& socket) {
+    last_renew_ = microsec_clock::universal_time();
+    Pkt6Ptr reply = reply_storage_.getRandom();
+    if (!reply) {
+        return (false);
+    }
+    Pkt6Ptr renew = createRenew(reply);
+    setDefaults6(socket, renew);
+    renew->pack();
+    IfaceMgr::instance().send(renew);
+    if (!stats_mgr6_) {
+        isc_throw(Unexpected, "Statistics Manager for DHCPv6 "
+                  "hasn't been initialized");
+    }
+    stats_mgr6_->passSentPacket(StatsMgr6::XCHG_RN, renew);
+    return (true);
+}
+
 void
 TestControl::sendRequest4(const TestControlSocket& socket,
                           const dhcp::Pkt4Ptr& discover_pkt4,
@@ -1916,16 +2052,17 @@ TestControl::testDiags(const char diag) const {
 }
 
 void
-TestControl::updateSendDue() {
+TestControl::updateSendDue(const boost::posix_time::ptime& last_sent,
+                           const int rate,
+                           boost::posix_time::ptime& send_due) {
     // If default constructor was called, this should not happen but
     // if somebody has changed default constructor it is better to
     // keep this check.
-    if (last_sent_.is_not_a_date_time()) {
+    if (last_sent.is_not_a_date_time()) {
         isc_throw(Unexpected, "time of last sent packet not initialized");
     }
     // Get the expected exchange rate.
     CommandOptions& options = CommandOptions::instance();
-    int rate = options.getRate();
     // If rate was not specified we will wait just one clock tick to
     // send next packet. This simulates best effort conditions.
     long duration = 1;
@@ -1937,14 +2074,14 @@ TestControl::updateSendDue() {
         duration = time_duration::ticks_per_second() / rate;
     }
     // Calculate due time to initiate next chunk of exchanges.
-    send_due_ = last_sent_ + time_duration(0, 0, 0, duration);
+    send_due = last_sent + time_duration(0, 0, 0, duration);
     // Check if it is already due.
     ptime now(microsec_clock::universal_time());
     // \todo verify if this condition is not too tight. In other words
     // verify if this will not produce too many late sends.
     // We might want to look at this once we are done implementing
     // microsecond timeouts in IfaceMgr.
-    if (now > send_due_) {
+    if (now > send_due) {
         if (testDiags('i')) {
             if (options.getIpVersion() == 4) {
                 stats_mgr4_->incrementCounter("latesend");

+ 80 - 11
tests/tools/perfdhcp/test_control.h

@@ -15,20 +15,21 @@
 #ifndef TEST_CONTROL_H
 #define TEST_CONTROL_H
 
-#include <string>
-#include <vector>
-
-#include <boost/noncopyable.hpp>
-#include <boost/shared_ptr.hpp>
-#include <boost/function.hpp>
-#include <boost/date_time/posix_time/posix_time.hpp>
+#include "packet_storage.h"
+#include "stats_mgr.h"
 
 #include <dhcp/iface_mgr.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt6.h>
 
-#include "stats_mgr.h"
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/function.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <string>
+#include <vector>
 
 namespace isc {
 namespace perfdhcp {
@@ -299,6 +300,27 @@ protected:
     /// \return true if any of the exit conditions is fulfilled.
     bool checkExitConditions() const;
 
+    /// \brief Removes cached DHCPv6 Reply packets every second.
+    ///
+    /// This function wipes cached Reply packets from the storage.
+    /// The number of packets left in the storage after the call
+    /// to this function should guarantee that the Renew packets
+    /// can be sent at the given rate. Note that the Renew packets
+    /// are generated for the existing leases, represented here as
+    /// replies from the server.
+    /// @todo Instead of cleaning packets periodically we could
+    /// just stop adding new packets when the certain threshold
+    /// has been reached.
+    void cleanCachedPackets();
+
+    /// \brief Creates IPv6 packet using options from Reply packet.
+    ///
+    /// \param reply An instance of the Reply packet which contents should
+    /// be used to create an instance of the Renew packet.
+    ///
+    /// \return created Renew packet.
+    dhcp::Pkt6Ptr createRenew(const dhcp::Pkt6Ptr& reply);
+
     /// \brief Factory function to create DHCPv6 ELAPSED_TIME option.
     ///
     /// This factory function creates DHCPv6 ELAPSED_TIME option instance.
@@ -450,6 +472,15 @@ protected:
         return (transid_gen_->generate());
     }
 
+    /// \brief Returns a timeout for packet reception.
+    ///
+    /// The calculation is based on the value of the timestamp
+    /// when the next set of packets is to be sent. If no packet is
+    /// received until then, new packets are sent.
+    ///
+    /// \return A current timeout in microseconds.
+    uint32_t getCurrentTimeout() const;
+
     /// \brief Returns number of exchanges to be started.
     ///
     /// Method returns number of new exchanges to be started as soon
@@ -457,8 +488,13 @@ protected:
     /// is based on current time, due time calculated with
     /// \ref updateSendDue function and expected rate.
     ///
+    /// \param send_due Due time to initiate next chunk set exchanges.
+    /// \param rate A rate at which exchanges are initiated.
+    ///
     /// \return number of exchanges to be started immediately.
-    uint64_t getNextExchangesNum() const;
+    static uint64_t
+    getNextExchangesNum(const boost::posix_time::ptime& send_due,
+                        const int rate);
 
     /// \brief Return template buffer.
     ///
@@ -702,6 +738,26 @@ protected:
                      const uint64_t packets_num,
                      const bool preload = false);
 
+    /// \brief Send number of DHCPv6 Renew packets to the server.
+    ///
+    /// \param socket An object representing socket to be used to send packets.
+    /// \param packets_num A number of Renew packets to be send.
+    ///
+    /// \return A number of packets actually sent.
+    uint64_t sendRenewPackets(const TestControlSocket& socket,
+                              const uint64_t packets_num);
+
+    /// \brief Send a renew message using provided socket.
+    ///
+    /// This method will select an existing lease from the Reply packet cache
+    /// If there is no lease that can be renewed this method will return false.
+    ///
+    /// \param socket An object encapsulating socket to be used to send
+    /// a packet.
+    ///
+    /// \return true if packet has been sent, false otherwise.
+    bool sendRenew(const TestControlSocket& socket);
+
     /// \brief Send DHCPv4 REQUEST message.
     ///
     /// Method creates and sends DHCPv4 REQUEST message to the server.
@@ -848,7 +904,14 @@ protected:
     /// Method updates due time to initiate next chunk of exchanges.
     /// Function takes current time, last sent packet's time and
     /// expected rate in its calculations.
-    void updateSendDue();
+    ///
+    /// \param last_sent A time when the last exchange was initiated.
+    /// \param rate A rate at which exchangesa re initiated
+    /// \param [out] send_due A reference to the time object to be updated
+    /// with the next due time.
+    void updateSendDue(const boost::posix_time::ptime& last_sent,
+                       const int rate,
+                       boost::posix_time::ptime& send_due);
 
 private:
 
@@ -996,13 +1059,19 @@ private:
     boost::posix_time::ptime send_due_;    ///< Due time to initiate next chunk
                                            ///< of exchanges.
     boost::posix_time::ptime last_sent_;   ///< Indicates when the last exchange
-                                           /// was initiated.
+                                           ///< was initiated.
+    boost::posix_time::ptime renew_due_;   ///< Due time to send next set of
+                                           ///< Renew requests.
+    boost::posix_time::ptime last_renew_;  ///< Indicates when the last Renew
+                                           ///< was attempted.
 
     boost::posix_time::ptime last_report_; ///< Last intermediate report time.
 
     StatsMgr4Ptr stats_mgr4_;  ///< Statistics Manager 4.
     StatsMgr6Ptr stats_mgr6_;  ///< Statistics Manager 6.
 
+    PacketStorage<dhcp::Pkt6> reply_storage_; ///< A storage for reply messages.
+
     NumberGeneratorPtr transid_gen_; ///< Transaction id generator.
     NumberGeneratorPtr macaddr_gen_; ///< Numbers generator for MAC address.
 

+ 1 - 0
tests/tools/perfdhcp/tests/Makefile.am

@@ -25,6 +25,7 @@ run_unittests_SOURCES += command_options_unittest.cc
 run_unittests_SOURCES += perf_pkt6_unittest.cc
 run_unittests_SOURCES += perf_pkt4_unittest.cc
 run_unittests_SOURCES += localized_option_unittest.cc
+run_unittests_SOURCES += packet_storage_unittest.cc
 run_unittests_SOURCES += stats_mgr_unittest.cc
 run_unittests_SOURCES += test_control_unittest.cc
 run_unittests_SOURCES += command_options_helper.h

+ 6 - 2
tests/tools/perfdhcp/tests/command_options_helper.h

@@ -15,11 +15,15 @@
 #ifndef COMMAND_OPTIONS_HELPER_H
 #define COMMAND_OPTIONS_HELPER_H
 
+#include "../command_options.h"
+#include <exceptions/exceptions.h>
+
+#include <assert.h>
+#include <iterator>
+#include <cstring>
 #include <string>
 #include <vector>
 
-#include <exceptions/exceptions.h>
-#include "../command_options.h"
 
 namespace isc {
 namespace perfdhcp {

+ 31 - 0
tests/tools/perfdhcp/tests/command_options_unittest.cc

@@ -334,6 +334,37 @@ TEST_F(CommandOptionsTest, Rate) {
                  isc::InvalidParameter);
 }
 
+TEST_F(CommandOptionsTest, RenewRate) {
+    CommandOptions& opt = CommandOptions::instance();
+    // If -f is specified together with -r the command line should
+    // be accepted and the renew rate should be set.
+    EXPECT_NO_THROW(process("perfdhcp -6 -r 10 -f 10 -l ethx all"));
+    EXPECT_EQ(10, opt.getRenewRate());
+    // Check that the release rate can be set to different value than
+    // rate specified as -r<rate>. Also, swap -f na d-r to make sure
+    // that order doesn't matter.
+    EXPECT_NO_THROW(process("perfdhcp -6 -f 5 -r 10 -l ethx all"));
+    EXPECT_EQ(5, opt.getRenewRate());
+    // The renew-rate of 0 is invalid.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -f 0 - l ethx all"),
+                 isc::InvalidParameter);
+    // If -r<rate> is not specified the -f<renew-rate> should not
+    // be accepted.
+    EXPECT_THROW(process("perfdhcp -6 -f 10 -l ethx all"),
+                 isc::InvalidParameter);
+    // Currently the -f<renew-rate> can be specified for IPv6 mode
+    // only.
+    EXPECT_THROW(process("perfdhcp -4 -r 10 -f 10 -l ethx all"),
+                 isc::InvalidParameter);
+    // Renew rate should be specified.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -f -l ethx all"),
+                 isc::InvalidParameter);
+
+    // -f and -i are mutually exclusive
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -f 10 -l ethx -i all"),
+                 isc::InvalidParameter);
+}
+
 TEST_F(CommandOptionsTest, ReportDelay) {
     CommandOptions& opt = CommandOptions::instance();
     EXPECT_NO_THROW(process("perfdhcp -r 100 -t 17 -l ethx all"));

+ 205 - 0
tests/tools/perfdhcp/tests/packet_storage_unittest.cc

@@ -0,0 +1,205 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "../packet_storage.h"
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt6.h>
+
+#include <gtest/gtest.h>
+
+namespace {
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace perfdhcp;
+
+/// @todo Implement the tests which use Pkt4 objects once the support for
+/// DHCPv4 renewals / releases is added.
+
+/// The number of packets in the test storage.
+const unsigned int STORAGE_SIZE = 20;
+
+/// Test fixture class for PacketStorage container testing.
+class PacketStorageTest : public ::testing::Test {
+public:
+    /// \brief Constructor, initializes the storage for each test.
+    PacketStorageTest() {
+        for (uint32_t i = 0; i < STORAGE_SIZE; ++i) {
+            storage_.append(createPacket6(DHCPV6_REPLY, i));
+        }
+    }
+
+    /// \brief Creates an instance of the Pkt6.
+    ///
+    /// \param packet_type A type of the packet.
+    /// \param transid Transaction id.
+    /// \return An instance of the Pkt6.
+    Pkt6Ptr createPacket6(const uint16_t packet_type,
+                          const uint32_t transid) {
+        return (Pkt6Ptr(new Pkt6(packet_type, transid)));
+    }
+
+    /// Packet storage under test.
+    PacketStorage<Pkt6> storage_;
+
+};
+
+// This test verifies that the packets in the storage can be accessed
+// sequentially and when a packet is returned, it is removed from the
+// storage. It also verifies the correctness of the behaviour of the
+// empty() and size() functions.
+TEST_F(PacketStorageTest, getNext) {
+    ASSERT_EQ(STORAGE_SIZE, storage_.size());
+    for (int i = 0; i < STORAGE_SIZE; ++i) {
+        Pkt6Ptr packet = storage_.getNext();
+        ASSERT_TRUE(packet) << "NULL packet returned by storage_.getNext() for"
+                            << " iteration number " << i;
+        EXPECT_EQ(i, packet->getTransid());
+        EXPECT_EQ(STORAGE_SIZE - i - 1, storage_.size());
+    }
+    EXPECT_TRUE(storage_.empty());
+    // When storage is empty, the attempt to get the next packet should
+    // result in returning NULL pointer.
+    EXPECT_FALSE(storage_.getNext());
+    // Let's try it again to see if the previous call to getNext didn't
+    // put the storage into the state in which the subsequent calls to
+    // getNext would result in incorrect behaviour.
+    EXPECT_FALSE(storage_.getNext());
+
+    // Let's add a new packet to the empty storage to check that storage
+    // "recovers" from being empty, i.e. that the internal indicator
+    // which points to current packet reinitializes correctly.
+    storage_.append(createPacket6(DHCPV6_REPLY, 100));
+    ASSERT_EQ(1, storage_.size());
+    Pkt6Ptr packet = storage_.getNext();
+    EXPECT_EQ(100, packet->getTransid());
+    EXPECT_FALSE(storage_.getNext());
+}
+
+// This test verifies that the packets in the storage can be accessed
+// randomly and when a packet is returned, it is removed from the
+// storage. It also verifies the correctness of the behaviour of the
+// empty() and size() functions.
+TEST_F(PacketStorageTest, getRandom) {
+    ASSERT_EQ(STORAGE_SIZE, storage_.size());
+    int cnt_equals = 0;
+    for (int i = 0; i < STORAGE_SIZE; ++i) {
+        Pkt6Ptr packet = storage_.getRandom();
+        ASSERT_TRUE(packet) << "NULL packet returned by storage_.getRandom()"
+            " for iteration number " << i;
+        EXPECT_EQ(STORAGE_SIZE - i - 1, storage_.size());
+        cnt_equals += (i == packet->getTransid() ? 1 : 0);
+    }
+    // If the number of times id is equal to i, is the same as the number
+    // of elements then they were NOT accessed randomly.
+    // The odds of 20 elements being randomly  accessed sequential order
+    // is nil isn't it?
+    EXPECT_NE(cnt_equals, STORAGE_SIZE);
+
+    EXPECT_TRUE(storage_.empty());
+    // When storage is empty, the attempt to get the random packet should
+    // result in returning NULL pointer.
+    EXPECT_FALSE(storage_.getRandom());
+    // Let's try it again to see if the previous call to getRandom didn't
+    // put the storage into the state in which the subsequent calls to
+    // getRandom would result in incorrect behaviour.
+    EXPECT_FALSE(storage_.getRandom());
+
+    // Let's add a new packet to the empty storage to check that storage
+    // "recovers" from being empty, i.e. that the internal indicator
+    // which points to the current packet reinitializes correctly.
+    storage_.append(createPacket6(DHCPV6_REPLY, 100));
+    ASSERT_EQ(1, storage_.size());
+    Pkt6Ptr packet = storage_.getRandom();
+    ASSERT_TRUE(packet);
+    EXPECT_EQ(100, packet->getTransid());
+    EXPECT_FALSE(storage_.getRandom());
+}
+
+// This test verifies that the packets in the storage can be accessed
+// either randomly or sequentially in the same time. It verifies that
+// each returned packet is removed from the storage.
+TEST_F(PacketStorageTest, getNextAndRandom) {
+    ASSERT_EQ(STORAGE_SIZE, storage_.size());
+    for (int i = 0; i < STORAGE_SIZE / 2; ++i) {
+        Pkt6Ptr packet_random = storage_.getRandom();
+        ASSERT_TRUE(packet_random) << "NULL packet returned by"
+            " storage_.getRandom() for iteration number " << i;
+        EXPECT_EQ(STORAGE_SIZE - 2 *i - 1, storage_.size());
+        Pkt6Ptr packet_seq = storage_.getNext();
+        ASSERT_TRUE(packet_seq) << "NULL packet returned by"
+            " storage_.getNext()  for iteration number " << i;
+        EXPECT_EQ(STORAGE_SIZE - 2 * i - 2, storage_.size());
+    }
+    EXPECT_TRUE(storage_.empty());
+    EXPECT_FALSE(storage_.getRandom());
+    EXPECT_FALSE(storage_.getNext());
+
+    // Append two packets to the storage to check if it can "recover"
+    // from being empty and that new elements can be accessed.
+    storage_.append(createPacket6(DHCPV6_REPLY, 100));
+    storage_.append(createPacket6(DHCPV6_REPLY, 101));
+    ASSERT_EQ(2, storage_.size());
+    // The newly added elements haven't been accessed yet. So, if we
+    // call getNext the first one should be returned.
+    Pkt6Ptr packet_next = storage_.getNext();
+    ASSERT_TRUE(packet_next);
+    // The first packet has transaction id equal to 100.
+    EXPECT_EQ(100, packet_next->getTransid());
+    // There should be just one packet left in the storage.
+    ASSERT_EQ(1, storage_.size());
+    // The call to getRandom should return the sole packet from the
+    // storage.
+    Pkt6Ptr packet_random = storage_.getRandom();
+    ASSERT_TRUE(packet_random);
+    EXPECT_EQ(101, packet_random->getTransid());
+    // Any further calls to getRandom and getNext should return NULL.
+    EXPECT_FALSE(storage_.getRandom());
+    EXPECT_FALSE(storage_.getNext());
+}
+
+// This test verifies that all packets are removed from the storage when
+// clear() function is invoked.
+TEST_F(PacketStorageTest, clearAll) {
+    ASSERT_EQ(STORAGE_SIZE, storage_.size());
+    ASSERT_NO_THROW(storage_.clear());
+    EXPECT_TRUE(storage_.empty());
+}
+
+// This test verifies that a set of packets can be removed from the
+// storage when a number of packets to be removed is specified. If
+// number of packets to be removed exceeds the storage size, all
+// packets should be removed.
+TEST_F(PacketStorageTest, clear) {
+    // Initially storage should have 20 elements.
+    ASSERT_EQ(STORAGE_SIZE, storage_.size());
+    // Remove 10 of them.
+    ASSERT_NO_THROW(storage_.clear(10));
+    // We should have 10 remaining.
+    ASSERT_EQ(10, storage_.size());
+
+    // Check that the retrieval still works after partial clear.
+    EXPECT_TRUE(storage_.getNext());
+    EXPECT_TRUE(storage_.getRandom());
+    // We should have 10 - 2 = 8 packets in the storage after retrieval.
+    ASSERT_EQ(8, storage_.size());
+
+    // Try to remove more elements that actually is. It
+    // should result in removal of all elements.
+    ASSERT_NO_THROW(storage_.clear(15));
+    EXPECT_TRUE(storage_.empty());
+}
+
+
+} // anonymous namespace

+ 182 - 15
tests/tools/perfdhcp/tests/test_control_unittest.cc

@@ -12,21 +12,22 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include "command_options_helper.h"
+#include "../test_control.h"
+
+#include <asiolink/io_address.h>
+#include <exceptions/exceptions.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/iface_mgr.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
 #include <cstddef>
 #include <stdint.h>
 #include <string>
 #include <fstream>
 #include <gtest/gtest.h>
 
-#include <boost/date_time/posix_time/posix_time.hpp>
-
-#include <exceptions/exceptions.h>
-#include <asiolink/io_address.h>
-#include <dhcp/dhcp4.h>
-#include <dhcp/iface_mgr.h>
-#include "command_options_helper.h"
-#include "../test_control.h"
-
 using namespace std;
 using namespace boost::posix_time;
 using namespace isc;
@@ -63,11 +64,18 @@ public:
         virtual uint32_t generate() {
             return (++transid_);
         }
+
+        /// \brief Return next transaction id value.
+        uint32_t getNext() const {
+            return (transid_ + 1);
+        }
+
     private:
         uint32_t transid_; ///< Last generated transaction id.
     };
 
     using TestControl::checkExitConditions;
+    using TestControl::createRenew;
     using TestControl::factoryElapsedTime6;
     using TestControl::factoryGeneric;
     using TestControl::factoryIana6;
@@ -85,6 +93,9 @@ public:
     using TestControl::processReceivedPacket6;
     using TestControl::registerOptionFactories;
     using TestControl::sendDiscover4;
+    using TestControl::sendPackets;
+    using TestControl::sendRenewPackets;
+    using TestControl::sendRequest6;
     using TestControl::sendSolicit6;
     using TestControl::setDefaults4;
     using TestControl::setDefaults6;
@@ -281,7 +292,7 @@ public:
         return (cnt);
     }
 
-    /// brief Test generation of mulitple DUIDs
+    /// \brief Test generation of mulitple DUIDs
     ///
     /// This method checks the generation of multiple DUIDs. Number
     /// of iterations depends on the number of simulated clients.
@@ -624,7 +635,6 @@ public:
         CommandOptionsHelper::process(cmdline);
     }
 
-private:
     /// \brief Create DHCPv4 OFFER packet.
     ///
     /// \param transid transaction id.
@@ -645,8 +655,8 @@ private:
     ///
     /// \param transid transaction id.
     /// \return instance of the packet.
-    boost::shared_ptr<Pkt6>
-    createAdvertisePkt6(uint32_t transid) const {
+    Pkt6Ptr
+    createAdvertisePkt6(const uint32_t transid) const {
         boost::shared_ptr<Pkt6> advertise(new Pkt6(DHCPV6_ADVERTISE, transid));
         // Add IA_NA if requested by the client.
         if (CommandOptions::instance().getLeaseType()
@@ -671,6 +681,33 @@ private:
         return (advertise);
     }
 
+    Pkt6Ptr
+    createReplyPkt6(const uint32_t transid) const {
+        Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, transid));
+        // Add IA_NA if requested by the client.
+        if (CommandOptions::instance().getLeaseType()
+            .includes(CommandOptions::LeaseType::ADDRESS)) {
+            OptionPtr opt_ia_na = Option::factory(Option::V6, D6O_IA_NA);
+            reply->addOption(opt_ia_na);
+        }
+        // Add IA_PD if requested by the client.
+        if (CommandOptions::instance().getLeaseType()
+            .includes(CommandOptions::LeaseType::PREFIX)) {
+            OptionPtr opt_ia_pd = Option::factory(Option::V6, D6O_IA_PD);
+            reply->addOption(opt_ia_pd);
+        }
+        OptionPtr opt_serverid(new Option(Option::V6, D6O_SERVERID));
+        NakedTestControl tc;
+        uint8_t randomized = 0;
+        std::vector<uint8_t> duid(tc.generateDuid(randomized));
+        OptionPtr opt_clientid(Option::factory(Option::V6, D6O_CLIENTID, duid));
+        reply->addOption(opt_serverid);
+        reply->addOption(opt_clientid);
+        reply->updateTimestamp();
+        return (reply);
+
+    }
+
 };
 
 TEST_F(TestControlTest, GenerateDuid) {
@@ -1197,7 +1234,8 @@ TEST_F(TestControlTest, RateControl) {
     CommandOptions& options = CommandOptions::instance();
 
     NakedTestControl tc1;
-    uint64_t xchgs_num = tc1.getNextExchangesNum();
+    uint64_t xchgs_num = tc1.getNextExchangesNum(microsec_clock::universal_time(),
+                                                 options.getRate());
     EXPECT_EQ(options.getAggressivity(), xchgs_num);
 
     // The exchange rate is now 1 per second. We don't know how many
@@ -1208,8 +1246,137 @@ TEST_F(TestControlTest, RateControl) {
         processCmdLine("perfdhcp -l 127.0.0.1 -a 1000000 -r 1 all")
     );
     NakedTestControl tc2;
-    xchgs_num = tc2.getNextExchangesNum();
+    xchgs_num = tc2.getNextExchangesNum(microsec_clock::universal_time(),
+                                        options.getRate());
     EXPECT_GT(xchgs_num, 0);
     EXPECT_LT(xchgs_num, options.getAggressivity());
     // @todo add more thorough checks for rate values.
 }
+
+TEST_F(TestControlTest, processRenew) {
+    std::string loopback_iface(getLocalLoopback());
+    if (loopback_iface.empty()) {
+        std::cout << "Skipping the test because loopback interface could"
+            " not be detected" << std::endl;
+        return;
+    }
+    // This command line specifies that the Renew messages should be sent
+    // with the same rate as the Solicit messages.
+    ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l " + loopback_iface +
+                                   " -r 10 -f 10 -R 10 -L 10547 -n 10 ::1"));
+    // Create a test controller class.
+    NakedTestControl tc;
+    tc.initializeStatsMgr();
+    // Set the transaction id generator to sequential to control to guarantee
+    // that transaction ids are predictable.
+    boost::shared_ptr<NakedTestControl::IncrementalGenerator>
+        generator(new NakedTestControl::IncrementalGenerator());
+    tc.setTransidGenerator(generator);
+    // Socket has to be created so as we can actually send packets.
+    int sock_handle = 0;
+    ASSERT_NO_THROW(sock_handle = tc.openSocket());
+    TestControl::TestControlSocket sock(sock_handle);
+
+    // Send a number of Solicit messages. Each generated Solicit will be
+    // assigned a different transaction id, starting from 1 to 10.
+    tc.sendPackets(sock, 10);
+
+    // Simulate Advertise responses from the server. Each advertise is assigned
+    // a transaction id from the range of 1 to 10, so as they match the
+    // transaction ids from the Solicit messages.
+    for (int i = generator->getNext() - 10; i < generator->getNext(); ++i) {
+        Pkt6Ptr advertise(createAdvertisePkt6(i));
+        // If Advertise is matched with the Solicit the call below will
+        // trigger a corresponding Request. They will be assigned
+        // transaction ids from the range from 11 to 20 (the range of
+        // 1 to 10 has been used by Solicit-Advertise).
+        ASSERT_NO_THROW(tc.processReceivedPacket6(sock, advertise));
+    }
+
+    // Requests have been sent, so now let's simulate responses from the server.
+    // Generate corresponding Reply messages with the transaction ids from the
+    // range from 11 to 20.
+    for (int i = generator->getNext() - 10; i < generator->getNext(); ++i) {
+        Pkt6Ptr reply(createReplyPkt6(i));
+        // Each Reply packet corresponds to the new lease acquired. Since
+        // -f<renew-rate> option has been specified, received Reply
+        // messages are held so as Renew messages can be sent for
+        // existing leases.
+        ASSERT_NO_THROW(tc.processReceivedPacket6(sock, reply));
+    }
+
+    uint64_t renew_num;
+    // Try to send 5 Renew packets. It should be successful because
+    // 10 Reply messages has been received. For each of them we should
+    // be able to send Renew.
+    ASSERT_NO_THROW(renew_num = tc.sendRenewPackets(sock, 5));
+    // Make sure that we have sent 5 packets.
+    EXPECT_EQ(5, renew_num);
+
+    // Try to do it again. We should still have 5 Reply packets for
+    // which Renews haven't been sent yet.
+    ASSERT_NO_THROW(renew_num = tc.sendRenewPackets(sock, 5));
+    EXPECT_EQ(5, renew_num);
+
+    // We used all the Reply packets (we sent Renew for each of them
+    // already). Therefore, no further Renew packets should be sent before
+    // We acquire new leases.
+    ASSERT_NO_THROW(renew_num = tc.sendRenewPackets(sock, 5));
+    // Make sure that no Renew has been sent.
+    EXPECT_EQ(0, renew_num);
+}
+
+TEST_F(TestControlTest, createRenew) {
+    // This command line specifies that the Renew messages should be sent
+    // with the same rate as the Solicit messages.
+    ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l lo -r 10 -f 10 -R 10"
+                                   " -L 10547 -n 10 -e address-and-prefix"
+                                   " ::1"));
+    // Create a test controller class.
+    NakedTestControl tc;
+    // Set the transaction id generator because createRenew function requires
+    // it to generate the transaction id for the Renew packet.
+    boost::shared_ptr<NakedTestControl::IncrementalGenerator>
+        generator(new NakedTestControl::IncrementalGenerator());
+    tc.setTransidGenerator(generator);
+
+    // Create a Reply packet. The createRenew function will need Reply
+    // packet to create a corresponding Renew.
+    Pkt6Ptr reply = createReplyPkt6(1);
+    Pkt6Ptr renew;
+    // Check that Renew is created.
+    ASSERT_NO_THROW(renew = tc.createRenew(reply));
+    ASSERT_TRUE(renew);
+    EXPECT_EQ(DHCPV6_RENEW, renew->getType());
+    EXPECT_EQ(1, renew->getTransid());
+
+    // Now check that the Renew packet created, has expected options. The
+    // payload of these options should be the same as the payload of the
+    // options in the Reply.
+
+    // Client Identifier
+    OptionPtr opt_clientid = renew->getOption(D6O_CLIENTID);
+    ASSERT_TRUE(opt_clientid);
+    EXPECT_TRUE(reply->getOption(D6O_CLIENTID)->getData() ==
+                opt_clientid->getData());
+
+    // Server identifier
+    OptionPtr opt_serverid = renew->getOption(D6O_SERVERID);
+    ASSERT_TRUE(opt_serverid);
+    EXPECT_TRUE(reply->getOption(D6O_SERVERID)->getData() ==
+                opt_serverid->getData());
+
+    // IA_NA
+    OptionPtr opt_ia_na = renew->getOption(D6O_IA_NA);
+    ASSERT_TRUE(opt_ia_na);
+    EXPECT_TRUE(reply->getOption(D6O_IA_NA)->getData() ==
+                opt_ia_na->getData());
+
+    // IA_PD
+    OptionPtr opt_ia_pd = renew->getOption(D6O_IA_PD);
+    ASSERT_TRUE(opt_ia_pd);
+    EXPECT_TRUE(reply->getOption(D6O_IA_PD)->getData() ==
+                opt_ia_pd->getData());
+
+}
+