Browse Source

[2246] Merge branch 'master' into trac2246 (includes #1824 fix)

Conflicts:
	ChangeLog
Tomek Mrugalski 11 years ago
parent
commit
113fef8620

+ 21 - 2
ChangeLog

@@ -4,6 +4,25 @@
 	contributing a patch.
 	contributing a patch.
 	(Trac #2246, git abcd)
 	(Trac #2246, git abcd)
 
 
+717.	[bug]		marcin
+	Fixed the bug which incorrectly treated DHCPv4 option codes 224-254 as
+	standard options, barring them from being used as custom options.
+	(Trac #2772, git c6158690c389d75686545459618ae0bf16f2cdb8)
+
+716.	[func]		marcin
+	perfdhcp: added support for sending DHCPv6 Relese messages at the specified
+	rate and measure performance. The orphan messages counters are not
+	displayed for individual exchanges anymore. The following ticket: #3261
+	has been submitted to implement global orphan counting for all exchange
+	types.
+	(Trac #3181, git 684524bc130080e4fa31b65edfd14d58eec37e50)
+
+715.	[bug]		marcin
+	libdhcp++: Used the CMSG_SPACE instead of CMSG_LEN macro to calculate
+	msg_controllen field of the DHCPv6 message. Use of CMSG_LEN causes
+	sendmsg failures on OpenBSD due to the bug kernel/6080 on OpenBSD.
+	(Trac #1824, git 39c9499d001a98c8d2f5792563c28a5eb2cc5fcb)
+
 714.	[doc]		tomek
 714.	[doc]		tomek
 	BIND10 Contributor's Guide added.
 	BIND10 Contributor's Guide added.
 	(Trac #3109, git 016bfae00460b4f88adbfd07ed26759eb294ef10)
 	(Trac #3109, git 016bfae00460b4f88adbfd07ed26759eb294ef10)
@@ -13,7 +32,7 @@
 	b10-dhcp-ddns.  The class now generates all DNS update request variations
 	b10-dhcp-ddns.  The class now generates all DNS update request variations
 	needed to fulfill it's state machine in compliance with RFC 4703, sections
 	needed to fulfill it's state machine in compliance with RFC 4703, sections
 	5.3 and 5.4.
 	5.3 and 5.4.
-	(Trac# 3207, git dceca9554cb9410dd8d12371b68198b797cb6cfb)
+	(Trac# 3241, git dceca9554cb9410dd8d12371b68198b797cb6cfb)
 
 
 712.	[func]		marcin,dclink
 712.	[func]		marcin,dclink
 	b10-dhcp4: If server fails to open a socket on one interface it
 	b10-dhcp4: If server fails to open a socket on one interface it
@@ -30,7 +49,7 @@
 	reverse DNS entries for a given FQDN.  It does not yet construct
 	reverse DNS entries for a given FQDN.  It does not yet construct
 	the actual DNS update requests, this will be added under Trac#
 	the actual DNS update requests, this will be added under Trac#
 	3241.
 	3241.
-	(Trac# 3207, git 8f99da735a9f39d514c40d0a295f751dc8edfbcd)
+	(Trac# 3087, git 8f99da735a9f39d514c40d0a295f751dc8edfbcd)
 
 
 710.	[build]		jinmei
 710.	[build]		jinmei
 	Fixed various build time issues for MacOS X 10.9.  Those include
 	Fixed various build time issues for MacOS X 10.9.  Those include

+ 13 - 3
src/lib/dhcp/iface_mgr.cc

@@ -26,7 +26,8 @@
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <util/io/pktinfo_utilities.h>
 #include <util/io/pktinfo_utilities.h>
 
 
-
+#include <cstring>
+#include <errno.h>
 #include <fstream>
 #include <fstream>
 #include <sstream>
 #include <sstream>
 
 
@@ -892,13 +893,22 @@ IfaceMgr::send(const Pkt6Ptr& pkt) {
     struct in6_pktinfo *pktinfo = convertPktInfo6(CMSG_DATA(cmsg));
     struct in6_pktinfo *pktinfo = convertPktInfo6(CMSG_DATA(cmsg));
     memset(pktinfo, 0, sizeof(struct in6_pktinfo));
     memset(pktinfo, 0, sizeof(struct in6_pktinfo));
     pktinfo->ipi6_ifindex = pkt->getIndex();
     pktinfo->ipi6_ifindex = pkt->getIndex();
-    m.msg_controllen = cmsg->cmsg_len;
+    // According to RFC3542, section 20.2, the msg_controllen field
+    // may be set using CMSG_SPACE (which includes padding) or
+    // using CMSG_LEN. Both forms appear to work fine on Linux, FreeBSD,
+    // NetBSD, but OpenBSD appears to have a bug, discussed here:
+    // http://www.archivum.info/mailing.openbsd.bugs/2009-02/00017/
+    // kernel-6080-msg_controllen-of-IPV6_PKTINFO.html
+    // which causes sendmsg to return EINVAL if the CMSG_LEN is
+    // used to set the msg_controllen value.
+    m.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
 
 
     pkt->updateTimestamp();
     pkt->updateTimestamp();
 
 
     result = sendmsg(getSocket(*pkt), &m, 0);
     result = sendmsg(getSocket(*pkt), &m, 0);
     if (result < 0) {
     if (result < 0) {
-        isc_throw(SocketWriteError, "Pkt6 send failed: sendmsg() returned " << result);
+        isc_throw(SocketWriteError, "pkt6 send failed: sendmsg() returned"
+                  " with an error: " << strerror(errno));
     }
     }
 
 
     return (result);
     return (result);

+ 2 - 2
src/lib/dhcp/libdhcp++.cc

@@ -163,9 +163,9 @@ LibDHCP::isStandardOption(const Option::Universe u, const uint16_t code) {
               code == 126 ||
               code == 126 ||
               code == 127 ||
               code == 127 ||
               (code > 146 && code < 150) ||
               (code > 146 && code < 150) ||
-              (code > 177  && code < 208) ||
+              (code > 177 && code < 208) ||
               (code > 213 && code <  220) ||
               (code > 213 && code <  220) ||
-              (code > 221 && code < 224))) {
+              (code > 221 && code < 255))) {
                 return (true);
                 return (true);
             }
             }
 
 

+ 5 - 2
src/lib/dhcp/pkt_filter_inet.cc

@@ -16,6 +16,8 @@
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt_filter_inet.h>
 #include <dhcp/pkt_filter_inet.h>
+#include <errno.h>
+#include <cstring>
 
 
 using namespace isc::asiolink;
 using namespace isc::asiolink;
 
 
@@ -235,14 +237,15 @@ PktFilterInet::send(const Iface&, uint16_t sockfd,
     struct in_pktinfo* pktinfo =(struct in_pktinfo *)CMSG_DATA(cmsg);
     struct in_pktinfo* pktinfo =(struct in_pktinfo *)CMSG_DATA(cmsg);
     memset(pktinfo, 0, sizeof(struct in_pktinfo));
     memset(pktinfo, 0, sizeof(struct in_pktinfo));
     pktinfo->ipi_ifindex = pkt->getIndex();
     pktinfo->ipi_ifindex = pkt->getIndex();
-    m.msg_controllen = cmsg->cmsg_len;
+    m.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo));
 #endif
 #endif
 
 
     pkt->updateTimestamp();
     pkt->updateTimestamp();
 
 
     int result = sendmsg(sockfd, &m, 0);
     int result = sendmsg(sockfd, &m, 0);
     if (result < 0) {
     if (result < 0) {
-        isc_throw(SocketWriteError, "pkt4 send failed");
+        isc_throw(SocketWriteError, "pkt4 send failed: sendmsg() returned "
+                  " with an error: " << strerror(errno));
     }
     }
 
 
     return (result);
     return (result);

+ 4 - 1
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -643,7 +643,10 @@ TEST_F(LibDhcpTest, isStandardOption4) {
                                           187, 188, 189, 190, 191, 192, 193, 194, 195,
                                           187, 188, 189, 190, 191, 192, 193, 194, 195,
                                           196, 197, 198, 199, 200, 201, 202, 203, 204,
                                           196, 197, 198, 199, 200, 201, 202, 203, 204,
                                           205, 206, 207, 214, 215, 216, 217, 218, 219,
                                           205, 206, 207, 214, 215, 216, 217, 218, 219,
-                                          222, 223 };
+                                          222, 223, 224, 225, 226, 227, 228, 229, 230,
+                                          231, 232, 233, 234, 235, 236, 237, 238, 239,
+                                          240, 241, 242, 243, 244, 245, 246, 247, 248,
+                                          249, 250, 251, 252, 253, 254 };
     const size_t unassigned_num = sizeof(unassigned_codes) / sizeof(unassigned_codes[0]);
     const size_t unassigned_num = sizeof(unassigned_codes) / sizeof(unassigned_codes[0]);
 
 
     // Try all possible option codes.
     // Try all possible option codes.

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

@@ -25,6 +25,7 @@ perfdhcp_SOURCES += perf_pkt6.cc perf_pkt6.h
 perfdhcp_SOURCES += perf_pkt4.cc perf_pkt4.h
 perfdhcp_SOURCES += perf_pkt4.cc perf_pkt4.h
 perfdhcp_SOURCES += packet_storage.h
 perfdhcp_SOURCES += packet_storage.h
 perfdhcp_SOURCES += pkt_transform.cc pkt_transform.h
 perfdhcp_SOURCES += pkt_transform.cc pkt_transform.h
+perfdhcp_SOURCES += rate_control.cc rate_control.h
 perfdhcp_SOURCES += stats_mgr.h
 perfdhcp_SOURCES += stats_mgr.h
 perfdhcp_SOURCES += test_control.cc test_control.h
 perfdhcp_SOURCES += test_control.cc test_control.h
 libb10_perfdhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
 libb10_perfdhcp___la_CXXFLAGS = $(AM_CXXFLAGS)

+ 40 - 15
tests/tools/perfdhcp/command_options.cc

@@ -114,6 +114,7 @@ CommandOptions::reset() {
     lease_type_.set(LeaseType::ADDRESS);
     lease_type_.set(LeaseType::ADDRESS);
     rate_ = 0;
     rate_ = 0;
     renew_rate_ = 0;
     renew_rate_ = 0;
+    release_rate_ = 0;
     report_delay_ = 0;
     report_delay_ = 0;
     clients_num_ = 0;
     clients_num_ = 0;
     mac_template_.assign(mac, mac + 6);
     mac_template_.assign(mac, mac + 6);
@@ -211,7 +212,7 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
     // In this section we collect argument values from command line
     // In this section we collect argument values from command line
     // they will be tuned and validated elsewhere
     // they will be tuned and validated elsewhere
     while((opt = getopt(argc, argv, "hv46r:t:R:b:n:p:d:D:l:P:a:L:"
     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:f:")) != -1) {
+                        "s:iBc1T:X:O:E:S:I:x:w:e:f:F:")) != -1) {
         stream << " -" << static_cast<char>(opt);
         stream << " -" << static_cast<char>(opt);
         if (optarg) {
         if (optarg) {
             stream << " " << optarg;
             stream << " " << optarg;
@@ -307,6 +308,12 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
                                           " must be a positive integer");
                                           " must be a positive integer");
             break;
             break;
 
 
+        case 'F':
+            release_rate_ = positiveInteger("value of the release rate:"
+                                            " -F<release-rate> must be a"
+                                            " positive integer");
+            break;
+
         case 'h':
         case 'h':
             usage();
             usage();
             return (true);
             return (true);
@@ -690,6 +697,8 @@ CommandOptions::validate() const {
           "-6 (IPv6) must be set to use -c");
           "-6 (IPv6) must be set to use -c");
     check((getIpVersion() != 6) && (getRenewRate() !=0),
     check((getIpVersion() != 6) && (getRenewRate() !=0),
           "-f<renew-rate> may be used with -6 (IPv6) only");
           "-f<renew-rate> may be used with -6 (IPv6) only");
+    check((getIpVersion() != 6) && (getReleaseRate() != 0),
+          "-F<release-rate> may be used with -6 (IPv6) only");
     check((getExchangeMode() == DO_SA) && (getNumRequests().size() > 1),
     check((getExchangeMode() == DO_SA) && (getNumRequests().size() > 1),
           "second -n<num-request> is not compatible with -i");
           "second -n<num-request> is not compatible with -i");
     check((getIpVersion() == 4) && !getLeaseType().is(LeaseType::ADDRESS),
     check((getIpVersion() == 4) && !getLeaseType().is(LeaseType::ADDRESS),
@@ -719,6 +728,8 @@ CommandOptions::validate() const {
           "-I<ip-offset> is not compatible with -i");
           "-I<ip-offset> is not compatible with -i");
     check((getExchangeMode() == DO_SA) && (getRenewRate() != 0),
     check((getExchangeMode() == DO_SA) && (getRenewRate() != 0),
           "-f<renew-rate> is not compatible with -i");
           "-f<renew-rate> is not compatible with -i");
+    check((getExchangeMode() == DO_SA) && (getReleaseRate() != 0),
+          "-F<release-rate> is not compatible with -i");
     check((getExchangeMode() != DO_SA) && (isRapidCommit() != 0),
     check((getExchangeMode() != DO_SA) && (isRapidCommit() != 0),
           "-i must be set to use -c");
           "-i must be set to use -c");
     check((getRate() == 0) && (getReportDelay() != 0),
     check((getRate() == 0) && (getReportDelay() != 0),
@@ -730,12 +741,16 @@ CommandOptions::validate() const {
     check((getRate() == 0) &&
     check((getRate() == 0) &&
           ((getMaxDrop().size() > 0) || getMaxDropPercentage().size() > 0),
           ((getMaxDrop().size() > 0) || getMaxDropPercentage().size() > 0),
           "-r<rate> must be set to use -D<max-drop>");
           "-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() + getReleaseRate() > getRate()),
+          "The sum of Renew rate (-f<renew-rate>) and Release rate"
+          " (-F<release-rate>) must not be greater than the exchange"
+          " rate specified as -r<rate>");
     check((getRate() == 0) && (getRenewRate() != 0),
     check((getRate() == 0) && (getRenewRate() != 0),
           "Renew rate specified as -f<renew-rate> must not be specified"
           "Renew rate specified as -f<renew-rate> must not be specified"
           " when -r<rate> parameter is not specified");
           " when -r<rate> parameter is not specified");
+    check((getRate() == 0) && (getReleaseRate() != 0),
+          "Release rate specified as -F<release-rate> must not be specified"
+          " when -r<rate> parameter is not specified");
     check((getTemplateFiles().size() < getTransactionIdOffset().size()),
     check((getTemplateFiles().size() < getTransactionIdOffset().size()),
           "-T<template-file> must be set to use -X<xid-offset>");
           "-T<template-file> must be set to use -X<xid-offset>");
     check((getTemplateFiles().size() < getRandomOffset().size()),
     check((getTemplateFiles().size() < getRandomOffset().size()),
@@ -816,6 +831,9 @@ CommandOptions::printCommandLine() const {
     if (getRenewRate() != 0) {
     if (getRenewRate() != 0) {
         std::cout << "renew-rate[1/s]=" << getRenewRate() << std::endl;
         std::cout << "renew-rate[1/s]=" << getRenewRate() << std::endl;
     }
     }
+    if (getReleaseRate() != 0) {
+        std::cout << "release-rate[1/s]=" << getReleaseRate() << std::endl;
+    }
     if (report_delay_ != 0) {
     if (report_delay_ != 0) {
         std::cout << "report[s]=" << report_delay_ << std::endl;
         std::cout << "report[s]=" << report_delay_ << std::endl;
     }
     }
@@ -899,13 +917,14 @@ void
 CommandOptions::usage() const {
 CommandOptions::usage() const {
     std::cout <<
     std::cout <<
         "perfdhcp [-hv] [-4|-6] [-e<lease-type>] [-r<rate>] [-f<renew-rate>]\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"
+        "         [-F<release-rate>] [-t<report>] [-R<range>] [-b<base>]\n"
+        "         [-n<num-request>] [-p<test-period>] [-d<drop-time>]\n"
+        "         [-D<max-drop>] [-l<local-addr|interface>] [-P<preload>]\n"
+        "         [-a<aggressivity>] [-L<local-port>] [-s<seed>] [-i] [-B]\n"
+        "         [-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>]\n"
+        "         [server]\n"
         "\n"
         "\n"
         "The [server] argument is the name/address of the DHCP server to\n"
         "The [server] argument is the name/address of the DHCP server to\n"
         "contact.  For DHCPv4 operation, exchanges are initiated by\n"
         "contact.  For DHCPv4 operation, exchanges are initiated by\n"
@@ -948,10 +967,6 @@ CommandOptions::usage() const {
         "-E<time-offset>: Offset of the (DHCPv4) secs field / (DHCPv6)\n"
         "-E<time-offset>: Offset of the (DHCPv4) secs field / (DHCPv6)\n"
         "    elapsed-time option in the (second/request) template.\n"
         "    elapsed-time option in the (second/request) template.\n"
         "    The value 0 disables it.\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"
         "-h: Print this help.\n"
         "-i: Do only the initial part of an exchange: DO or SA, depending on\n"
         "-i: Do only the initial part of an exchange: DO or SA, depending on\n"
         "    whether -6 is given.\n"
         "    whether -6 is given.\n"
@@ -999,6 +1014,16 @@ CommandOptions::usage() const {
         "\n"
         "\n"
         "DHCPv6 only options:\n"
         "DHCPv6 only options:\n"
         "-c: Add a rapid commit option (exchanges will be SA).\n"
         "-c: Add a rapid commit option (exchanges will be SA).\n"
+        "-f<renew-rate>: Rate at which IPv6 Renew requests are sent to\n"
+        "    a server. This value is only valid when used in conjunction with\n"
+        "    the exchange rate (given by -r<rate>).  Furthermore the sum of\n"
+        "    this value and the release-rate (given by -F<rate) must be equal\n"
+        "    to or less than the exchange rate.\n"
+        "-F<release-rate>: Rate at which IPv6 Release requests are sent to\n"
+        "    a server. This value is only valid when used in conjunction with\n"
+        "    the exchange rate (given by -r<rate>).  Furthermore the sum of\n"
+        "    this value and the renew-rate (given by -f<rate) must be equal\n"
+        "    to or less than the exchange rate.\n"
         "\n"
         "\n"
         "The remaining options are used only in conjunction with -r:\n"
         "The remaining options are used only in conjunction with -r:\n"
         "\n"
         "\n"

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

@@ -1,4 +1,3 @@
-
 // Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
 // Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
@@ -156,11 +155,16 @@ public:
     /// \return exchange rate per second.
     /// \return exchange rate per second.
     int getRate() const { return rate_; }
     int getRate() const { return rate_; }
 
 
-    /// \brief Returns a rate at which IPv6 Renew messages are sent.
+    /// \brief Returns a rate at which DHCPv6 Renew messages are sent.
     ///
     ///
     /// \return 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_); }
     int getRenewRate() const { return (renew_rate_); }
 
 
+    /// \brief Returns a rate at which DHCPv6 Release messages are sent.
+    ///
+    /// \return A rate at which DHCPv6 Release messages are sent.
+    int getReleaseRate() const { return (release_rate_); }
+
     /// \brief Returns delay between two performance reports.
     /// \brief Returns delay between two performance reports.
     ///
     ///
     /// \return delay between two consecutive performance reports.
     /// \return delay between two consecutive performance reports.
@@ -469,6 +473,8 @@ private:
     int rate_;
     int rate_;
     /// A rate at which DHCPv6 Renew messages are sent.
     /// A rate at which DHCPv6 Renew messages are sent.
     int renew_rate_;
     int renew_rate_;
+    /// A rate at which DHCPv6 Release messages are sent.
+    int release_rate_;
     /// Delay between generation of two consecutive
     /// Delay between generation of two consecutive
     /// performance reports
     /// performance reports
     int report_delay_;
     int report_delay_;

+ 158 - 0
tests/tools/perfdhcp/rate_control.cc

@@ -0,0 +1,158 @@
+// 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 <exceptions/exceptions.h>
+#include <tests/tools/perfdhcp/rate_control.h>
+
+namespace isc {
+namespace perfdhcp {
+
+using namespace boost::posix_time;
+
+RateControl::RateControl()
+    : send_due_(currentTime()), last_sent_(currentTime()),
+      aggressivity_(1), rate_(0), late_sent_(false) {
+}
+
+RateControl::RateControl(const int rate, const int aggressivity)
+    : send_due_(currentTime()), last_sent_(currentTime()),
+      aggressivity_(aggressivity), rate_(rate), late_sent_(false) {
+    if (aggressivity_ < 1) {
+        isc_throw(isc::BadValue, "invalid value of aggressivity "
+                  << aggressivity << ", expected value is greater than 0");
+    }
+    if (rate_ < 0) {
+        isc_throw(isc::BadValue, "invalid value of rate " << rate
+                  << ", expected non-negative value");
+    }
+}
+
+uint64_t
+RateControl::getOutboundMessageCount() {
+
+    // We need calculate the due time for sending next set of messages.
+    updateSendDue();
+
+    // Get current time. If we are behind due time, we have to calculate
+    // how many messages to send to catch up with the rate.
+    ptime now = currentTime();
+    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 (getRate() != 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.
+            double due_factor = duration.fractional_seconds() /
+                time_duration::ticks_per_second();
+            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 * getRate());
+            // We want to make sure that at least one packet goes out.
+            if (due_exchanges == 0) {
+                due_exchanges = 1;
+            }
+            // We should not exceed aggressivity as it could have been
+            // restricted from command line.
+            if (due_exchanges > getAggressivity()) {
+                due_exchanges = getAggressivity();
+            }
+        } else {
+            // Rate is not specified so we rely on aggressivity
+            // which is the number of packets to be sent in
+            // one chunk.
+            due_exchanges = getAggressivity();
+        }
+        return (due_exchanges);
+    }
+    return (0);
+}
+
+boost::posix_time::ptime
+RateControl::currentTime() {
+    return (microsec_clock::universal_time());
+}
+
+void
+RateControl::updateSendDue() {
+    // There is no sense to update due time if the current due time is in the
+    // future. The due time is calculated as a duration between the moment
+    // when the last message of the given type was sent and the time when
+    // next one is supposed to be sent based on a given rate. The former value
+    // will not change until we send the next message, which we don't do
+    // until we reach the due time.
+    if (send_due_ > currentTime()) {
+        return;
+    }
+    // This is initialized in the class constructor, so if it is not initialized
+    // it is a programmatic error.
+    if (last_sent_.is_not_a_date_time()) {
+        isc_throw(isc::Unexpected, "timestamp of the last sent packet not"
+                  " initialized");
+    }
+    // If rate was not specified we will wait just one clock tick to
+    // send next packet. This simulates best effort conditions.
+    long duration = 1;
+    if (getRate() != 0) {
+        // We use number of ticks instead of nanoseconds because
+        // nanosecond resolution may not be available on some
+        // machines. Number of ticks guarantees the highest possible
+        // timer resolution.
+        duration = time_duration::ticks_per_second() / getRate();
+    }
+    // Calculate due time to initiate next chunk of exchanges.
+    send_due_ = last_sent_ + time_duration(0, 0, 0, duration);
+    if (send_due_ > currentTime()) {
+        late_sent_ = true;
+    } else {
+        late_sent_ = false;
+    }
+}
+
+void
+RateControl::setAggressivity(const int aggressivity) {
+    if (aggressivity < 1) {
+        isc_throw(isc::BadValue, "invalid value of aggressivity "
+                  << aggressivity << ", expected value is greater than 0");
+    }
+    aggressivity_ = aggressivity;
+}
+
+void
+RateControl::setRate(const int rate) {
+    if (rate < 0) {
+        isc_throw(isc::BadValue, "invalid value of rate " << rate
+                  << ", expected non-negative value");
+    }
+    rate_ = rate;
+}
+
+void
+RateControl::setRelativeDue(const int offset) {
+    send_due_ = offset > 0 ?
+        currentTime() + seconds(abs(offset)) :
+        currentTime() - seconds(abs(offset));
+}
+
+void
+RateControl::updateSendTime() {
+    last_sent_ = currentTime();
+}
+
+} // namespace perfdhcp
+} // namespace isc

+ 180 - 0
tests/tools/perfdhcp/rate_control.h

@@ -0,0 +1,180 @@
+// 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 RATE_CONTROL_H
+#define RATE_CONTROL_H
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief A message sending rate control class for perfdhcp.
+///
+/// This class provides the means to control the rate at which messages
+/// of the specific type are sent by perfdhcp. Each message type,
+/// for which the desired rate can be specified, has a corresponding
+/// \c RateControl object. So, the perfdhcp is using up to three objects
+/// of this type at the same time, to control the rate of the following
+/// messages being sent:
+/// - Discover(DHCPv4) or Solicit (DHCPv6)
+/// - Renew (DHCPv6) or Request (DHCPv4) to renew leases.
+/// - Release
+///
+/// The purpose of the RateControl class is to track the due time for
+/// sending next message (or bunch of messages) to keep outbound rate
+/// of particular messages at the desired level. The due time is calculated
+/// using the desired rate value and the timestamp when the last message of
+/// the particular type has been sent. That puts the responsibility on the
+/// \c TestControl class to invoke the \c RateControl::updateSendDue, every
+/// time the message is sent.
+///
+/// The \c RateControl object returns the number of messages to be sent at
+/// the time. The number returned is 0, if perfdhcp shouldn't send any messages
+/// yet, or 1 (sometimes more) if the send due time has been reached.
+class RateControl {
+public:
+
+    /// \brief Default constructor.
+    RateControl();
+
+    /// \brief Constructor which sets desired rate and aggressivity.
+    ///
+    /// \param rate A desired rate.
+    /// \param aggressivity A desired aggressivity.
+    RateControl(const int rate, const int aggressivity);
+
+    /// \brief Returns the value of aggressivity.
+    int getAggressivity() const {
+        return (aggressivity_);
+    }
+
+    /// \brief Returns current due time to send next message.
+    boost::posix_time::ptime getDue() const {
+        return (send_due_);
+    }
+
+    /// \brief Returns number of messages to be sent "now".
+    ///
+    /// This function calculates how many messages of the given type should
+    /// be sent immediately when the call to the function returns, to catch
+    /// up with the desired message rate.
+    ///
+    /// The value returned depends on the due time calculated with the
+    /// \c RateControl::updateSendDue function and the current time. If
+    /// the due time has been hit, the non-zero number of messages is returned.
+    /// If the due time hasn't been hit, the number returned is 0.
+    ///
+    /// If the rate is non-zero, the number of messages to be sent is calculated
+    /// as follows:
+    /// \code
+    ///          num = duration * rate
+    /// \endcode
+    /// where <b>duration</b> is a time period between the due time to send
+    /// next set of messages and current time. The duration is expressed in
+    /// seconds with the fractional part having 6 or 9 digits (depending on
+    /// the timer resolution). If the calculated value is equal to 0, it is
+    /// rounded to 1, so as at least one message is sent.
+    ///
+    /// The value of aggressivity limits the maximal number of messages to
+    /// be sent one after another. If the number of messages calculated with
+    /// the equation above exceeds the aggressivity, this function will return
+    /// the value equal to aggressivity.
+    ///
+    /// If the rate is not specified (equal to 0), the value calculated by
+    /// this function is equal to aggressivity.
+    ///
+    /// \return A number of messages to be sent immediately.
+    uint64_t getOutboundMessageCount();
+
+    /// \brief Returns the rate.
+    int getRate() const {
+        return (rate_);
+    }
+
+    /// \brief Returns the value of the late send flag.
+    ///
+    /// The flag returned by this function indicates whether the new due time
+    /// calculated by the \c RateControl::updateSendDue is in the past.
+    /// This value is used by the \c TestControl object to increment the counter
+    /// of the late sent messages in the \c StatsMgr.
+    bool isLateSent() const {
+        return (late_sent_);
+    }
+
+    /// \brief Sets the value of aggressivity.
+    ///
+    /// \param aggressivity A new value of aggressivity. This value must be
+    /// a positive integer.
+    /// \throw isc::BadValue if new value is not a positive integer.
+    void setAggressivity(const int aggressivity);
+
+    /// \brief Sets the new rate.
+    ///
+    /// \param rate A new value of rate. This value must not be negative.
+    /// \throw isc::BadValue if new rate is negative.
+    void setRate(const int rate);
+
+    /// \brief Sets the value of the due time.
+    ///
+    /// This function is intended for unit testing. It manipulates the value of
+    /// the due time. The parameter passed to this function specifies the
+    /// (positive or negative) number of seconds relative to current time.
+    ///
+    /// \param offset A number of seconds relative to current time which
+    /// constitutes the new due time.
+    void setRelativeDue(const int offset);
+
+    /// \brief Sets the timestamp of the last sent message to current time.
+    void updateSendTime();
+
+protected:
+
+    /// \brief Convenience function returning current time.
+    ///
+    /// \return current time.
+    static boost::posix_time::ptime currentTime();
+
+    /// \brief Calculates the send due.
+    ///
+    /// This function calculates the send due timestamp using the current time
+    /// and desired rate. The due timestamp is calculated as a sum of the
+    /// timestamp when the last message was sent and the reciprocal of the rate
+    /// in micro or nanoseconds (depending on the timer resolution). If the rate
+    /// is not specified, the duration between two consecutive sends is one
+    /// timer tick.
+    void updateSendDue();
+
+    /// \brief Holds a timestamp when the next message should be sent.
+    boost::posix_time::ptime send_due_;
+
+    /// \brief Holds a timestamp when the last message was sent.
+    boost::posix_time::ptime last_sent_;
+
+    /// \brief Holds an aggressivity value.
+    int aggressivity_;
+
+    /// \brief Holds a desired rate value.
+    int rate_;
+
+    /// \brief A flag which indicates that the calculated due time is in the
+    /// past.
+    bool late_sent_;
+
+};
+
+}
+}
+
+#endif

+ 29 - 5
tests/tools/perfdhcp/stats_mgr.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -124,7 +124,8 @@ public:
         XCHG_RA,  ///< DHCPv4 REQUEST-ACK
         XCHG_RA,  ///< DHCPv4 REQUEST-ACK
         XCHG_SA,  ///< DHCPv6 SOLICIT-ADVERTISE
         XCHG_SA,  ///< DHCPv6 SOLICIT-ADVERTISE
         XCHG_RR,  ///< DHCPv6 REQUEST-REPLY
         XCHG_RR,  ///< DHCPv6 REQUEST-REPLY
-        XCHG_RN   ///< DHCPv6 RENEW-REPLY
+        XCHG_RN,  ///< DHCPv6 RENEW-REPLY
+        XCHG_RL   ///< DHCPv6 RELEASE-REPLY
     };
     };
 
 
     /// \brief Exchange Statistics.
     /// \brief Exchange Statistics.
@@ -632,12 +633,19 @@ public:
         /// Method prints main statistics for particular exchange.
         /// Method prints main statistics for particular exchange.
         /// Statistics includes: number of sent and received packets,
         /// Statistics includes: number of sent and received packets,
         /// number of dropped packets and number of orphans.
         /// number of dropped packets and number of orphans.
+        ///
+        /// \todo Currently the number of orphans is not displayed because
+        /// Reply messages received for Renew and Releases are counted as
+        /// orphans for the 4-way exchanges, which is wrong. We will need to
+        /// move the orphans counting out of the Statistics Manager so as
+        /// orphans counter is increased only if the particular message is
+        /// not identified as a reponse to any of the messages sent by perfdhcp.
         void printMainStats() const {
         void printMainStats() const {
             using namespace std;
             using namespace std;
             cout << "sent packets: " << getSentPacketsNum() << endl
             cout << "sent packets: " << getSentPacketsNum() << endl
                  << "received packets: " << getRcvdPacketsNum() << endl
                  << "received packets: " << getRcvdPacketsNum() << endl
-                 << "drops: " << getDroppedPacketsNum() << endl
-                 << "orphans: " << getOrphans() << endl;
+                 << "drops: " << getDroppedPacketsNum() << endl;
+            //                 << "orphans: " << getOrphans() << endl;
         }
         }
 
 
         /// \brief Print round trip time packets statistics.
         /// \brief Print round trip time packets statistics.
@@ -871,6 +879,20 @@ public:
                                                boot_time_));
                                                boot_time_));
     }
     }
 
 
+    /// \brief Check if the exchange type has been specified.
+    ///
+    /// This method checks if the \ref ExchangeStats object of a particular type
+    /// exists (has been added using \ref addExchangeStats function).
+    ///
+    /// \param xchg_type A type of the exchange being repersented by the
+    /// \ref ExchangeStats object.
+    ///
+    /// \return true if the \ref ExchangeStats object has been added for a
+    /// specified exchange type.
+    bool hasExchangeStats(const ExchangeType xchg_type) const {
+        return (exchanges_.find(xchg_type) != exchanges_.end());
+    }
+
     /// \brief Add named custom uint64 counter.
     /// \brief Add named custom uint64 counter.
     ///
     ///
     /// Method creates new named counter and stores in counter's map under
     /// Method creates new named counter and stores in counter's map under
@@ -1159,7 +1181,7 @@ public:
     ///
     ///
     /// \param xchg_type exchange type.
     /// \param xchg_type exchange type.
     /// \return string representing name of the exchange.
     /// \return string representing name of the exchange.
-    std::string exchangeToString(ExchangeType xchg_type) const {
+    static std::string exchangeToString(ExchangeType xchg_type) {
         switch(xchg_type) {
         switch(xchg_type) {
         case XCHG_DO:
         case XCHG_DO:
             return("DISCOVER-OFFER");
             return("DISCOVER-OFFER");
@@ -1171,6 +1193,8 @@ public:
             return("REQUEST-REPLY");
             return("REQUEST-REPLY");
         case XCHG_RN:
         case XCHG_RN:
             return("RENEW-REPLY");
             return("RENEW-REPLY");
+        case XCHG_RL:
+            return("RELEASE-REPLY");
         default:
         default:
             return("Unknown exchange type");
             return("Unknown exchange type");
         }
         }

+ 148 - 140
tests/tools/perfdhcp/test_control.cc

@@ -98,6 +98,21 @@ TestControl::TestControl() {
 }
 }
 
 
 void
 void
+TestControl::checkLateMessages(RateControl& rate_control) {
+    // If diagnostics is disabled, there is no need to log late sent messages.
+    // If it is enabled and the rate control object indicates that the last
+    // sent message was late, bump up the counter in Stats Manager.
+    if (rate_control.isLateSent() && testDiags('i')) {
+        CommandOptions& options = CommandOptions::instance();
+        if (options.getIpVersion() == 4) {
+            stats_mgr4_->incrementCounter("latesend");
+        } else if (options.getIpVersion() == 6) {
+            stats_mgr6_->incrementCounter("latesend");
+        }
+    }
+}
+
+void
 TestControl::cleanCachedPackets() {
 TestControl::cleanCachedPackets() {
     CommandOptions& options = CommandOptions::instance();
     CommandOptions& options = CommandOptions::instance();
     // When Renews are not sent, Reply packets are not cached so there
     // When Renews are not sent, Reply packets are not cached so there
@@ -316,28 +331,43 @@ TestControl::checkExitConditions() const {
 }
 }
 
 
 Pkt6Ptr
 Pkt6Ptr
-TestControl::createRenew(const Pkt6Ptr& reply) {
+TestControl::createMessageFromReply(const uint16_t msg_type,
+                                    const dhcp::Pkt6Ptr& reply) {
+    // Restrict messages to Release and Renew.
+    if (msg_type != DHCPV6_RENEW && msg_type != DHCPV6_RELEASE) {
+        isc_throw(isc::BadValue, "invalid message type " << msg_type
+                  << " to be created from Reply, expected DHCPV6_RENEW or"
+                  " DHCPV6_RELEASE");
+    }
+    // Get the string representation of the message - to be used for error
+    // logging purposes.
+    const char* msg_type_str = (msg_type == DHCPV6_RENEW ? "Renew" : "Release");
+    // Reply message must be specified.
     if (!reply) {
     if (!reply) {
-        isc_throw(isc::BadValue,"Unable to create Renew packet from the Reply packet"
-                  " because the instance of the Reply is NULL");
+        isc_throw(isc::BadValue, "Unable to create " << msg_type_str
+                  << " message from the Reply message because the instance of"
+                  " the Reply message is NULL");
     }
     }
-    Pkt6Ptr renew(new Pkt6(DHCPV6_RENEW, generateTransid()));
+
+    Pkt6Ptr msg(new Pkt6(msg_type, generateTransid()));
     // Client id.
     // Client id.
     OptionPtr opt_clientid = reply->getOption(D6O_CLIENTID);
     OptionPtr opt_clientid = reply->getOption(D6O_CLIENTID);
     if (!opt_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");
+        isc_throw(isc::Unexpected, "failed to create " << msg_type_str
+                  << " message because client id option has not been found"
+                  " in the Reply message");
     }
     }
-    renew->addOption(opt_clientid);
+    msg->addOption(opt_clientid);
     // Server id.
     // Server id.
     OptionPtr opt_serverid = reply->getOption(D6O_SERVERID);
     OptionPtr opt_serverid = reply->getOption(D6O_SERVERID);
     if (!opt_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");
+        isc_throw(isc::Unexpected, "failed to create " << msg_type_str
+                  << " because server id option has not been found in the"
+                  " Reply message");
     }
     }
-    renew->addOption(opt_serverid);
-    copyIaOptions(reply, renew);
-    return (renew);
+    msg->addOption(opt_serverid);
+    copyIaOptions(reply, msg);
+    return (msg);
 }
 }
 
 
 OptionPtr
 OptionPtr
@@ -489,16 +519,28 @@ TestControl::getCurrentTimeout() const {
     ptime now(microsec_clock::universal_time());
     ptime now(microsec_clock::universal_time());
     // Check that we haven't passed the moment to send the next set of
     // Check that we haven't passed the moment to send the next set of
     // packets.
     // packets.
-    if (now >= send_due_ ||
-        (options.getRenewRate() != 0 && now >= renew_due_)) {
+    if (now >= basic_rate_control_.getDue() ||
+        (options.getRenewRate() != 0 && now >= renew_rate_control_.getDue()) ||
+        (options.getReleaseRate() != 0 &&
+         now >= release_rate_control_.getDue())) {
         return (0);
         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());
+    // Let's assume that the due time for Solicit is the soonest.
+    ptime due = basic_rate_control_.getDue();
+    // If we are sending Renews and due time for Renew occurs sooner,
+    // set the due time to Renew due time.
+    if ((options.getRenewRate()) != 0 && (renew_rate_control_.getDue() < due)) {
+        due = renew_rate_control_.getDue();
+    }
+    // If we are sending Releases and the due time for Release occurs
+    // sooner than the current due time, let's use the due for Releases.
+    if ((options.getReleaseRate() != 0) &&
+        (release_rate_control_.getDue() < due)) {
+        due = release_rate_control_.getDue();
+    }
+    // Return the timeout in microseconds.
+    return (time_period(now, due).length().total_microseconds());
 }
 }
 
 
 int
 int
@@ -529,49 +571,6 @@ TestControl::getElapsedTime(const T& pkt1, const T& pkt2) {
     return(elapsed_period.length().total_milliseconds());
     return(elapsed_period.length().total_milliseconds());
 }
 }
 
 
-
-uint64_t
-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) {
-        // Reset number of exchanges.
-        uint64_t due_exchanges = 0;
-        // If rate is specified from the command line we have to
-        // synchornize with it.
-        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.
-            double due_factor = duration.fractional_seconds() /
-                time_duration::ticks_per_second();
-            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 * rate);
-            // We want to make sure that at least one packet goes out.
-            if (due_exchanges == 0) {
-                due_exchanges = 1;
-            }
-            // We should not exceed aggressivity as it could have been
-            // restricted from command line.
-            if (due_exchanges > options.getAggressivity()) {
-                due_exchanges = options.getAggressivity();
-            }
-        } else {
-            // Rate is not specified so we rely on aggressivity
-            // which is the number of packets to be sent in
-            // one chunk.
-            due_exchanges = options.getAggressivity();
-        }
-        return (due_exchanges);
-    }
-    return (0);
-}
-
 int
 int
 TestControl::getRandomOffset(const int arg_idx) const {
 TestControl::getRandomOffset(const int arg_idx) const {
     int rand_offset = CommandOptions::instance().getIpVersion() == 4 ?
     int rand_offset = CommandOptions::instance().getIpVersion() == 4 ?
@@ -695,6 +694,9 @@ TestControl::initializeStatsMgr() {
         if (options.getRenewRate() != 0) {
         if (options.getRenewRate() != 0) {
             stats_mgr6_->addExchangeStats(StatsMgr6::XCHG_RN);
             stats_mgr6_->addExchangeStats(StatsMgr6::XCHG_RN);
         }
         }
+        if (options.getReleaseRate() != 0) {
+            stats_mgr6_->addExchangeStats(StatsMgr6::XCHG_RL);
+        }
     }
     }
     if (testDiags('i')) {
     if (testDiags('i')) {
         if (options.getIpVersion() == 4) {
         if (options.getIpVersion() == 4) {
@@ -849,14 +851,15 @@ TestControl::sendPackets(const TestControlSocket& socket,
 }
 }
 
 
 uint64_t
 uint64_t
-TestControl::sendRenewPackets(const TestControlSocket& socket,
-                              const uint64_t packets_num) {
-    for (uint64_t i = 0; i < packets_num; ++i) {
-        if (!sendRenew(socket)) {
+TestControl::sendMultipleMessages6(const TestControlSocket& socket,
+                                   const uint32_t msg_type,
+                                   const uint64_t msg_num) {
+    for (uint64_t i = 0; i < msg_num; ++i) {
+        if (!sendMessageFromReply(msg_type, socket)) {
             return (i);
             return (i);
         }
         }
     }
     }
-    return (packets_num);
+    return (msg_num);
 }
 }
 
 
 void
 void
@@ -1114,14 +1117,36 @@ TestControl::processReceivedPacket6(const TestControlSocket& socket,
             }
             }
         }
         }
     } else if (packet_type == DHCPV6_REPLY) {
     } else if (packet_type == DHCPV6_REPLY) {
-        Pkt6Ptr sent_packet = stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RR,
-                                                          pkt6);
-        if (sent_packet) {
-            if (CommandOptions::instance().getRenewRate() != 0) {
+        // If the received message is Reply, we have to find out which exchange
+        // type the Reply message belongs to. It is doable by matching the Reply
+        // transaction id with the transaction id of the sent Request, Renew
+        // or Release. First we start with the Request.
+        if (stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RR, pkt6)) {
+            // The Reply belongs to Request-Reply exchange type. So, we may need
+            // to keep this Reply in the storage if Renews or/and Releases are
+            // being sent. Note that, Reply messages hold the information about
+            // leases assigned. We use this information to construct Renew and
+            // Release messages.
+            if (stats_mgr6_->hasExchangeStats(StatsMgr6::XCHG_RN) ||
+                stats_mgr6_->hasExchangeStats(StatsMgr6::XCHG_RL)) {
+                // Renew or Release messages are sent, because StatsMgr has the
+                // specific exchange type specified. Let's append the Reply
+                // message to a storage.
                 reply_storage_.append(pkt6);
                 reply_storage_.append(pkt6);
             }
             }
-        } else {
-            stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RN, pkt6);
+        // The Reply message is not a server's response to the Request message
+        // sent within the 4-way exchange. It may be a response to the Renew
+        // or Release message. In the if clause we first check if StatsMgr
+        // has exchange type for Renew specified, and if it has, if there is
+        // a corresponding Renew message for the received Reply. If not,
+        // we check that StatsMgr has exchange type for Release specified,
+        // as possibly the Reply has been sent in response to Release.
+        } else if (!(stats_mgr6_->hasExchangeStats(StatsMgr6::XCHG_RN) &&
+                     stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RN, pkt6)) &&
+                   stats_mgr6_->hasExchangeStats(StatsMgr6::XCHG_RL)) {
+            // At this point, it is only possible that the Reply has been sent
+            // in response to a Release. Try to match the Reply with Release.
+            stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RL, pkt6);
         }
         }
     }
     }
 }
 }
@@ -1250,12 +1275,16 @@ TestControl::registerOptionFactories() const {
 
 
 void
 void
 TestControl::reset() {
 TestControl::reset() {
-    send_due_ = microsec_clock::universal_time();
-    last_sent_ = send_due_;
-    last_report_ = send_due_;
-    renew_due_ = send_due_;
-    last_renew_ = send_due_;
+    CommandOptions& options = CommandOptions::instance();
+    basic_rate_control_.setAggressivity(options.getAggressivity());
+    basic_rate_control_.setRate(options.getRate());
+    renew_rate_control_.setAggressivity(options.getAggressivity());
+    renew_rate_control_.setRate(options.getRenewRate());
+    release_rate_control_.setAggressivity(options.getAggressivity());
+    release_rate_control_.setRate(options.getReleaseRate());
+
     transid_gen_.reset();
     transid_gen_.reset();
+    last_report_ = microsec_clock::universal_time();
     // Actual generators will have to be set later on because we need to
     // Actual generators will have to be set later on because we need to
     // get command line parameters first.
     // get command line parameters first.
     setTransidGenerator(NumberGeneratorPtr());
     setTransidGenerator(NumberGeneratorPtr());
@@ -1321,11 +1350,10 @@ TestControl::run() {
     // Initialize Statistics Manager. Release previous if any.
     // Initialize Statistics Manager. Release previous if any.
     initializeStatsMgr();
     initializeStatsMgr();
     for (;;) {
     for (;;) {
-        // Calculate send due based on when last exchange was initiated.
-        updateSendDue(last_sent_, options.getRate(), send_due_);
         // Calculate number of packets to be sent to stay
         // Calculate number of packets to be sent to stay
         // catch up with rate.
         // catch up with rate.
-        uint64_t packets_due = getNextExchangesNum(send_due_, options.getRate());
+        uint64_t packets_due = basic_rate_control_.getOutboundMessageCount();
+        checkLateMessages(basic_rate_control_);
         if ((packets_due == 0) && testDiags('i')) {
         if ((packets_due == 0) && testDiags('i')) {
             if (options.getIpVersion() == 4) {
             if (options.getIpVersion() == 4) {
                 stats_mgr4_->incrementCounter("shortwait");
                 stats_mgr4_->incrementCounter("shortwait");
@@ -1351,11 +1379,21 @@ TestControl::run() {
         // If -f<renew-rate> option was specified we have to check how many
         // 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.
         // Renew packets should be sent to catch up with a desired rate.
         if ((options.getIpVersion() == 6) && (options.getRenewRate() != 0)) {
         if ((options.getIpVersion() == 6) && (options.getRenewRate() != 0)) {
-            updateSendDue(last_renew_, options.getRenewRate(), renew_due_);
             uint64_t renew_packets_due =
             uint64_t renew_packets_due =
-                getNextExchangesNum(renew_due_, options.getRenewRate());
-            // Send renew packets.
-            sendRenewPackets(socket, renew_packets_due);
+                renew_rate_control_.getOutboundMessageCount();
+            checkLateMessages(renew_rate_control_);
+            // Send Renew messages.
+            sendMultipleMessages6(socket, DHCPV6_RENEW, renew_packets_due);
+        }
+
+        // If -F<release-rate> option was specified we have to check how many
+        // Release messages should be sent to catch up with a desired rate.
+        if ((options.getIpVersion() == 6) && (options.getReleaseRate() != 0)) {
+            uint64_t release_packets_due =
+                release_rate_control_.getOutboundMessageCount();
+            checkLateMessages(release_rate_control_);
+            // Send Release messages.
+            sendMultipleMessages6(socket, DHCPV6_RELEASE, release_packets_due);
         }
         }
 
 
         // Report delay means that user requested printing number
         // Report delay means that user requested printing number
@@ -1451,7 +1489,7 @@ TestControl::saveFirstPacket(const Pkt6Ptr& pkt) {
 void
 void
 TestControl::sendDiscover4(const TestControlSocket& socket,
 TestControl::sendDiscover4(const TestControlSocket& socket,
                            const bool preload /*= false*/) {
                            const bool preload /*= false*/) {
-    last_sent_ = microsec_clock::universal_time();
+    basic_rate_control_.updateSendTime();
     // Generate the MAC address to be passed in the packet.
     // Generate the MAC address to be passed in the packet.
     uint8_t randomized = 0;
     uint8_t randomized = 0;
     std::vector<uint8_t> mac_address = generateMacAddress(randomized);
     std::vector<uint8_t> mac_address = generateMacAddress(randomized);
@@ -1496,9 +1534,7 @@ void
 TestControl::sendDiscover4(const TestControlSocket& socket,
 TestControl::sendDiscover4(const TestControlSocket& socket,
                            const std::vector<uint8_t>& template_buf,
                            const std::vector<uint8_t>& template_buf,
                            const bool preload /* = false */) {
                            const bool preload /* = false */) {
-    // last_sent_ has to be updated for each function that initiates
-    // new transaction. The packet exchange synchronization relies on this.
-    last_sent_ = microsec_clock::universal_time();
+    basic_rate_control_.updateSendTime();
     // Get the first argument if mulitple the same arguments specified
     // Get the first argument if mulitple the same arguments specified
     // in the command line. First one refers to DISCOVER packets.
     // in the command line. First one refers to DISCOVER packets.
     const uint8_t arg_idx = 0;
     const uint8_t arg_idx = 0;
@@ -1547,21 +1583,35 @@ TestControl::sendDiscover4(const TestControlSocket& socket,
 }
 }
 
 
 bool
 bool
-TestControl::sendRenew(const TestControlSocket& socket) {
-    last_renew_ = microsec_clock::universal_time();
+TestControl::sendMessageFromReply(const uint16_t msg_type,
+                                  const TestControlSocket& socket) {
+    // We only permit Release or Renew messages to be sent using this function.
+    if (msg_type != DHCPV6_RENEW && msg_type != DHCPV6_RELEASE) {
+        isc_throw(isc::BadValue, "invalid message type " << msg_type
+                  << " to be sent, expected DHCPV6_RENEW or DHCPV6_RELEASE");
+    }
+    // We track the timestamp of last Release and Renew in different variables.
+    if (msg_type == DHCPV6_RENEW) {
+        renew_rate_control_.updateSendTime();
+    } else {
+        release_rate_control_.updateSendTime();
+    }
     Pkt6Ptr reply = reply_storage_.getRandom();
     Pkt6Ptr reply = reply_storage_.getRandom();
     if (!reply) {
     if (!reply) {
         return (false);
         return (false);
     }
     }
-    Pkt6Ptr renew = createRenew(reply);
-    setDefaults6(socket, renew);
-    renew->pack();
-    IfaceMgr::instance().send(renew);
+    // Prepare the message of the specified type.
+    Pkt6Ptr msg = createMessageFromReply(msg_type, reply);
+    setDefaults6(socket, msg);
+    msg->pack();
+    // And send it.
+    IfaceMgr::instance().send(msg);
     if (!stats_mgr6_) {
     if (!stats_mgr6_) {
         isc_throw(Unexpected, "Statistics Manager for DHCPv6 "
         isc_throw(Unexpected, "Statistics Manager for DHCPv6 "
                   "hasn't been initialized");
                   "hasn't been initialized");
     }
     }
-    stats_mgr6_->passSentPacket(StatsMgr6::XCHG_RN, renew);
+    stats_mgr6_->passSentPacket((msg_type == DHCPV6_RENEW ? StatsMgr6::XCHG_RN
+                                 : StatsMgr6::XCHG_RL), msg);
     return (true);
     return (true);
 }
 }
 
 
@@ -1903,7 +1953,7 @@ TestControl::sendRequest6(const TestControlSocket& socket,
 void
 void
 TestControl::sendSolicit6(const TestControlSocket& socket,
 TestControl::sendSolicit6(const TestControlSocket& socket,
                           const bool preload /*= false*/) {
                           const bool preload /*= false*/) {
-    last_sent_ = microsec_clock::universal_time();
+    basic_rate_control_.updateSendTime();
     // Generate DUID to be passed to the packet
     // Generate DUID to be passed to the packet
     uint8_t randomized = 0;
     uint8_t randomized = 0;
     std::vector<uint8_t> duid = generateDuid(randomized);
     std::vector<uint8_t> duid = generateDuid(randomized);
@@ -1952,7 +2002,7 @@ void
 TestControl::sendSolicit6(const TestControlSocket& socket,
 TestControl::sendSolicit6(const TestControlSocket& socket,
                           const std::vector<uint8_t>& template_buf,
                           const std::vector<uint8_t>& template_buf,
                           const bool preload /*= false*/) {
                           const bool preload /*= false*/) {
-    last_sent_ = microsec_clock::universal_time();
+    basic_rate_control_.updateSendTime();
     const int arg_idx = 0;
     const int arg_idx = 0;
     // Get transaction id offset.
     // Get transaction id offset.
     size_t transid_offset = getTransactionIdOffset(arg_idx);
     size_t transid_offset = getTransactionIdOffset(arg_idx);
@@ -2051,47 +2101,5 @@ TestControl::testDiags(const char diag) const {
     return (false);
     return (false);
 }
 }
 
 
-void
-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()) {
-        isc_throw(Unexpected, "time of last sent packet not initialized");
-    }
-    // Get the expected exchange rate.
-    CommandOptions& options = CommandOptions::instance();
-    // If rate was not specified we will wait just one clock tick to
-    // send next packet. This simulates best effort conditions.
-    long duration = 1;
-    if (rate != 0) {
-        // We use number of ticks instead of nanoseconds because
-        // nanosecond resolution may not be available on some
-        // machines. Number of ticks guarantees the highest possible
-        // timer resolution.
-        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);
-    // 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 (testDiags('i')) {
-            if (options.getIpVersion() == 4) {
-                stats_mgr4_->incrementCounter("latesend");
-            } else if (options.getIpVersion() == 6) {
-                stats_mgr6_->incrementCounter("latesend");
-            }
-        }
-    }
-}
-
-
 } // namespace perfdhcp
 } // namespace perfdhcp
 } // namespace isc
 } // namespace isc

+ 50 - 52
tests/tools/perfdhcp/test_control.h

@@ -15,8 +15,9 @@
 #ifndef TEST_CONTROL_H
 #ifndef TEST_CONTROL_H
 #define TEST_CONTROL_H
 #define TEST_CONTROL_H
 
 
-#include "packet_storage.h"
-#include "stats_mgr.h"
+#include <tests/tools/perfdhcp/packet_storage.h>
+#include <tests/tools/perfdhcp/rate_control.h>
+#include <tests/tools/perfdhcp/stats_mgr.h>
 
 
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/dhcp6.h>
@@ -313,13 +314,23 @@ protected:
     /// has been reached.
     /// has been reached.
     void cleanCachedPackets();
     void cleanCachedPackets();
 
 
-    /// \brief Creates IPv6 packet using options from Reply packet.
+    /// \brief Creates DHCPv6 message from the Reply packet.
     ///
     ///
+    /// This function creates DHCPv6 Renew or Release message using the
+    /// data from the Reply message by copying options from the Reply
+    /// message.
+    ///
+    /// \param msg_type A type of the message to be createad.
     /// \param reply An instance of the Reply packet which contents should
     /// \param reply An instance of the Reply packet which contents should
-    /// be used to create an instance of the Renew packet.
+    /// be used to create an instance of the new message.
     ///
     ///
-    /// \return created Renew packet.
-    dhcp::Pkt6Ptr createRenew(const dhcp::Pkt6Ptr& reply);
+    /// \return created Release or Renew message
+    /// \throw isc::BadValue if the msg_type is neither DHCPV6_RENEW nor
+    /// DHCPV6_RELEASE or if the reply is NULL.
+    /// \throw isc::Unexpected if mandatory options are missing in the
+    /// Reply message.
+    dhcp::Pkt6Ptr createMessageFromReply(const uint16_t msg_type,
+                                         const dhcp::Pkt6Ptr& reply);
 
 
     /// \brief Factory function to create DHCPv6 ELAPSED_TIME option.
     /// \brief Factory function to create DHCPv6 ELAPSED_TIME option.
     ///
     ///
@@ -481,21 +492,6 @@ protected:
     /// \return A current timeout in microseconds.
     /// \return A current timeout in microseconds.
     uint32_t getCurrentTimeout() const;
     uint32_t getCurrentTimeout() const;
 
 
-    /// \brief Returns number of exchanges to be started.
-    ///
-    /// Method returns number of new exchanges to be started as soon
-    /// as possible to satisfy expected rate. Calculation used here
-    /// 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.
-    static uint64_t
-    getNextExchangesNum(const boost::posix_time::ptime& send_due,
-                        const int rate);
-
     /// \brief Return template buffer.
     /// \brief Return template buffer.
     ///
     ///
     /// Method returns template buffer at specified index.
     /// Method returns template buffer at specified index.
@@ -738,25 +734,32 @@ protected:
                      const uint64_t packets_num,
                      const uint64_t packets_num,
                      const bool preload = false);
                      const bool preload = false);
 
 
-    /// \brief Send number of DHCPv6 Renew packets to the server.
+    /// \brief Send number of DHCPv6 Renew or Release messages to the server.
     ///
     ///
     /// \param socket An object representing socket to be used to send packets.
     /// \param socket An object representing socket to be used to send packets.
-    /// \param packets_num A number of Renew packets to be send.
+    /// \param msg_type A type of the messages to be sent (DHCPV6_RENEW or
+    /// DHCPV6_RELEASE).
+    /// \param msg_num A number of messages to be sent.
     ///
     ///
-    /// \return A number of packets actually sent.
-    uint64_t sendRenewPackets(const TestControlSocket& socket,
-                              const uint64_t packets_num);
+    /// \return A number of messages actually sent.
+    uint64_t sendMultipleMessages6(const TestControlSocket& socket,
+                                   const uint32_t msg_type,
+                                   const uint64_t msg_num);
 
 
-    /// \brief Send a renew message using provided socket.
+    /// \brief Send DHCPv6 Renew or Release message using specified socket.
     ///
     ///
     /// This method will select an existing lease from the Reply packet cache
     /// 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.
+    /// If there is no lease that can be renewed or released this method will
+    /// return false.
     ///
     ///
+    /// \param msg_type A type of the message to be sent (DHCPV6_RENEW or
+    /// DHCPV6_RELEASE).
     /// \param socket An object encapsulating socket to be used to send
     /// \param socket An object encapsulating socket to be used to send
     /// a packet.
     /// a packet.
     ///
     ///
-    /// \return true if packet has been sent, false otherwise.
-    bool sendRenew(const TestControlSocket& socket);
+    /// \return true if the message has been sent, false otherwise.
+    bool sendMessageFromReply(const uint16_t msg_type,
+                              const TestControlSocket& socket);
 
 
     /// \brief Send DHCPv4 REQUEST message.
     /// \brief Send DHCPv4 REQUEST message.
     ///
     ///
@@ -899,21 +902,18 @@ protected:
     /// \return true if diagnostics flag has been set.
     /// \return true if diagnostics flag has been set.
     bool testDiags(const char diag) const;
     bool testDiags(const char diag) const;
 
 
-    /// \brief Update due time to initiate next chunk of exchanges.
+protected:
+
+    /// \brief Increments counter of late sent messages if required.
     ///
     ///
-    /// 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.
+    /// This function checks if the message or set of messages of a given type,
+    /// were sent later than their due time. If they were sent late, it is
+    /// an indication that the perfdhcp doesn't catch up with the desired rate
+    /// for sending messages.
     ///
     ///
-    /// \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:
+    /// \param rate_control An object tracking due times for a particular
+    /// type of messages.
+    void checkLateMessages(RateControl& rate_control);
 
 
     /// \brief Copies IA_NA or IA_PD option from one packet to another.
     /// \brief Copies IA_NA or IA_PD option from one packet to another.
     ///
     ///
@@ -943,7 +943,7 @@ private:
 
 
     /// \brief Calculate elapsed time between two packets.
     /// \brief Calculate elapsed time between two packets.
     ///
     ///
-    /// \param T Pkt4Ptr or Pkt6Ptr class.
+    /// \tparam T Pkt4Ptr or Pkt6Ptr class.
     /// \param pkt1 first packet.
     /// \param pkt1 first packet.
     /// \param pkt2 second packet.
     /// \param pkt2 second packet.
     /// \throw InvalidOperation if packet timestamps are invalid.
     /// \throw InvalidOperation if packet timestamps are invalid.
@@ -1056,14 +1056,12 @@ private:
     std::string vector2Hex(const std::vector<uint8_t>& vec,
     std::string vector2Hex(const std::vector<uint8_t>& vec,
                            const std::string& separator = "") const;
                            const std::string& separator = "") const;
 
 
-    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.
-    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.
+    /// \brief A rate control class for Discover and Solicit messages.
+    RateControl basic_rate_control_;
+    /// \brief A rate control class for Renew messages.
+    RateControl renew_rate_control_;
+    /// \brief A rate control class for Release messages.
+    RateControl release_rate_control_;
 
 
     boost::posix_time::ptime last_report_; ///< Last intermediate report time.
     boost::posix_time::ptime last_report_; ///< Last intermediate report time.
 
 

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

@@ -26,6 +26,7 @@ run_unittests_SOURCES += perf_pkt6_unittest.cc
 run_unittests_SOURCES += perf_pkt4_unittest.cc
 run_unittests_SOURCES += perf_pkt4_unittest.cc
 run_unittests_SOURCES += localized_option_unittest.cc
 run_unittests_SOURCES += localized_option_unittest.cc
 run_unittests_SOURCES += packet_storage_unittest.cc
 run_unittests_SOURCES += packet_storage_unittest.cc
+run_unittests_SOURCES += rate_control_unittest.cc
 run_unittests_SOURCES += stats_mgr_unittest.cc
 run_unittests_SOURCES += stats_mgr_unittest.cc
 run_unittests_SOURCES += test_control_unittest.cc
 run_unittests_SOURCES += test_control_unittest.cc
 run_unittests_SOURCES += command_options_helper.h
 run_unittests_SOURCES += command_options_helper.h
@@ -33,6 +34,7 @@ run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/command_options.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/pkt_transform.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/pkt_transform.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt6.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt6.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt4.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt4.cc
+run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/rate_control.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/test_control.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/test_control.cc
 
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)

+ 66 - 2
tests/tools/perfdhcp/tests/command_options_unittest.cc

@@ -168,6 +168,8 @@ protected:
         EXPECT_EQ(CommandOptions::DORA_SARR, opt.getExchangeMode());
         EXPECT_EQ(CommandOptions::DORA_SARR, opt.getExchangeMode());
         EXPECT_TRUE(opt.getLeaseType().is(CommandOptions::LeaseType::ADDRESS));
         EXPECT_TRUE(opt.getLeaseType().is(CommandOptions::LeaseType::ADDRESS));
         EXPECT_EQ(0, opt.getRate());
         EXPECT_EQ(0, opt.getRate());
+        EXPECT_EQ(0, opt.getRenewRate());
+        EXPECT_EQ(0, opt.getReleaseRate());
         EXPECT_EQ(0, opt.getReportDelay());
         EXPECT_EQ(0, opt.getReportDelay());
         EXPECT_EQ(0, opt.getClientsNum());
         EXPECT_EQ(0, opt.getClientsNum());
 
 
@@ -341,12 +343,18 @@ TEST_F(CommandOptionsTest, RenewRate) {
     EXPECT_NO_THROW(process("perfdhcp -6 -r 10 -f 10 -l ethx all"));
     EXPECT_NO_THROW(process("perfdhcp -6 -r 10 -f 10 -l ethx all"));
     EXPECT_EQ(10, opt.getRenewRate());
     EXPECT_EQ(10, opt.getRenewRate());
     // Check that the release rate can be set to different value than
     // 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
+    // rate specified as -r<rate>. Also, swap -f and -r to make sure
     // that order doesn't matter.
     // that order doesn't matter.
     EXPECT_NO_THROW(process("perfdhcp -6 -f 5 -r 10 -l ethx all"));
     EXPECT_NO_THROW(process("perfdhcp -6 -f 5 -r 10 -l ethx all"));
     EXPECT_EQ(5, opt.getRenewRate());
     EXPECT_EQ(5, opt.getRenewRate());
+    // The renew rate should not be greater than the rate.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -f 11 -l ethx all"),
+                 isc::InvalidParameter);
     // The renew-rate of 0 is invalid.
     // The renew-rate of 0 is invalid.
-    EXPECT_THROW(process("perfdhcp -6 -r 10 -f 0 - l ethx all"),
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -f 0 -l ethx all"),
+                 isc::InvalidParameter);
+    // The negative renew-rate is invalid.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -f -5 -l ethx all"),
                  isc::InvalidParameter);
                  isc::InvalidParameter);
     // If -r<rate> is not specified the -f<renew-rate> should not
     // If -r<rate> is not specified the -f<renew-rate> should not
     // be accepted.
     // be accepted.
@@ -365,6 +373,62 @@ TEST_F(CommandOptionsTest, RenewRate) {
                  isc::InvalidParameter);
                  isc::InvalidParameter);
 }
 }
 
 
+TEST_F(CommandOptionsTest, ReleaseRate) {
+    CommandOptions& opt = CommandOptions::instance();
+    // If -F is specified together with -r the command line should
+    // be accepted and the release rate should be set.
+    EXPECT_NO_THROW(process("perfdhcp -6 -r 10 -F 10 -l ethx all"));
+    EXPECT_EQ(10, opt.getReleaseRate());
+    // Check that the release rate can be set to different value than
+    // rate specified as -r<rate>. Also, swap -F and -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.getReleaseRate());
+    // The release rate should not be greater than the rate.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -F 11 -l ethx all"),
+                 isc::InvalidParameter);
+    // The release-rate of 0 is invalid.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -F 0 -l ethx all"),
+                 isc::InvalidParameter);
+    // The negative rlease-rate is invalid.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -F -5 -l ethx all"),
+                 isc::InvalidParameter);
+    // If -r<rate> is not specified the -F<release-rate> should not
+    // be accepted.
+    EXPECT_THROW(process("perfdhcp -6 -F 10 -l ethx all"),
+                 isc::InvalidParameter);
+    // Currently the -F<release-rate> can be specified for IPv6 mode
+    // only.
+    EXPECT_THROW(process("perfdhcp -4 -r 10 -F 10 -l ethx all"),
+                 isc::InvalidParameter);
+    // Release 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, ReleaseRenew) {
+    CommandOptions& opt = CommandOptions::instance();
+    // It should be possible to specify the -F, -f and -r options.
+    EXPECT_NO_THROW(process("perfdhcp -6 -r 10 -F 3 -f 5 -l ethx all"));
+    EXPECT_EQ(10, opt.getRate());
+    EXPECT_EQ(3, opt.getReleaseRate());
+    EXPECT_EQ(5, opt.getRenewRate());
+    // It should be possible to specify the -F and -f with the values which
+    // sum is equal to the rate specified as -r<rate>.
+    EXPECT_NO_THROW(process("perfdhcp -6 -r 8 -F 3 -f 5 -l ethx all"));
+    EXPECT_EQ(8, opt.getRate());
+    EXPECT_EQ(3, opt.getReleaseRate());
+    EXPECT_EQ(5, opt.getRenewRate());
+    // Check that the sum of the release and renew rate is not greater
+    // than the rate specified as -r<rate>.
+    EXPECT_THROW(process("perfdhcp -6 -F 6 -f 5 -r 10 -l ethx all"),
+                 isc::InvalidParameter);
+}
+
 TEST_F(CommandOptionsTest, ReportDelay) {
 TEST_F(CommandOptionsTest, ReportDelay) {
     CommandOptions& opt = CommandOptions::instance();
     CommandOptions& opt = CommandOptions::instance();
     EXPECT_NO_THROW(process("perfdhcp -r 100 -t 17 -l ethx all"));
     EXPECT_NO_THROW(process("perfdhcp -r 100 -t 17 -l ethx all"));

+ 207 - 0
tests/tools/perfdhcp/tests/rate_control_unittest.cc

@@ -0,0 +1,207 @@
+// 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 <exceptions/exceptions.h>
+#include <tests/tools/perfdhcp/rate_control.h>
+#include <gtest/gtest.h>
+
+
+using namespace isc;
+using namespace isc::perfdhcp;
+
+/// \brief A class which exposes protected methods and members of the
+/// RateControl class (under test).
+class NakedRateControl : public RateControl {
+public:
+
+    /// \brief Default constructor.
+    NakedRateControl()
+        : RateControl() {
+    }
+
+    /// \brief Constructor which sets up the rate and aggressivity.
+    ///
+    /// \param rate A rate at which messages are sent.
+    /// \param aggressivity A value of aggressivity. This value controls the
+    /// maximal number of messages sent in one chunk.
+    NakedRateControl(const int rate, const int aggressivity)
+        : RateControl(rate, aggressivity) {
+    }
+
+    using RateControl::currentTime;
+    using RateControl::updateSendTime;
+    using RateControl::updateSendDue;
+    using RateControl::send_due_;
+    using RateControl::last_sent_;
+    using RateControl::late_sent_;
+
+};
+
+// Test default constructor.
+TEST(RateControl, constructorDefault) {
+    NakedRateControl rc;
+    EXPECT_EQ(1, rc.getAggressivity());
+    EXPECT_EQ(0, rc.getRate());
+    EXPECT_FALSE(rc.getDue().is_not_a_date_time());
+    EXPECT_FALSE(rc.last_sent_.is_not_a_date_time());
+    EXPECT_FALSE(rc.isLateSent());
+}
+
+// Test the constructor which sets the rate and aggressivity.
+TEST(RateControl, constructor) {
+    // Call the constructor and verify that it sets the appropriate
+    // values.
+    NakedRateControl rc1(3, 2);
+    EXPECT_EQ(2, rc1.getAggressivity());
+    EXPECT_EQ(3, rc1.getRate());
+    EXPECT_FALSE(rc1.getDue().is_not_a_date_time());
+    EXPECT_FALSE(rc1.last_sent_.is_not_a_date_time());
+    EXPECT_FALSE(rc1.isLateSent());
+
+    // Call the constructor again and make sure that different values
+    // will be set correctly.
+    NakedRateControl rc2(5, 6);
+    EXPECT_EQ(6, rc2.getAggressivity());
+    EXPECT_EQ(5, rc2.getRate());
+    EXPECT_FALSE(rc2.getDue().is_not_a_date_time());
+    EXPECT_FALSE(rc2.last_sent_.is_not_a_date_time());
+    EXPECT_FALSE(rc2.isLateSent());
+
+    // The 0 value of aggressivity < 1 is not acceptable.
+    EXPECT_THROW(RateControl(3, 0), isc::BadValue);
+    // The negative value of rate is not acceptable.
+    EXPECT_THROW(RateControl(-1, 3), isc::BadValue);
+}
+
+// Check the aggressivity accessor.
+TEST(RateControl, getAggressivity) {
+    RateControl rc;
+    ASSERT_EQ(1, rc.getAggressivity());
+    rc.setAggressivity(5);
+    ASSERT_EQ(5, rc.getAggressivity());
+    rc.setAggressivity(10);
+    EXPECT_EQ(10, rc.getAggressivity());
+}
+
+// Check the due time accessor.
+TEST(RateControl, getDue) {
+    NakedRateControl rc;
+    ASSERT_FALSE(rc.getDue().is_not_a_date_time());
+    rc.send_due_ = NakedRateControl::currentTime();
+    EXPECT_TRUE(NakedRateControl::currentTime() >= rc.getDue());
+    rc.send_due_ = NakedRateControl::currentTime() +
+        boost::posix_time::seconds(10);
+    EXPECT_TRUE(NakedRateControl::currentTime() < rc.getDue());
+}
+
+// Check the rate accessor.
+TEST(RateControl, getRate) {
+    RateControl rc;
+    ASSERT_EQ(0, rc.getRate());
+    rc.setRate(5);
+    ASSERT_EQ(5, rc.getRate());
+    rc.setRate(10);
+    EXPECT_EQ(10, rc.getRate());
+}
+
+// Check if late send flag accessor.
+TEST(RateControl, isLateSent) {
+    NakedRateControl rc;
+    ASSERT_FALSE(rc.isLateSent());
+    rc.late_sent_ = true;
+    EXPECT_TRUE(rc.isLateSent());
+}
+
+// Check that the function returns the number of messages to be sent "now"
+// correctly.
+// @todo Possibly extend this test to cover more complex scenarios. Note that
+// it is quite hard to fully test this function as its behaviour strongly
+// depends on time.
+TEST(RateControl, getOutboundMessageCount) {
+    NakedRateControl rc1(1000, 1);
+    // Set the timestamp of the last sent message well to the past.
+    // The resulting due time will be in the past too.
+    rc1.last_sent_ =
+        NakedRateControl::currentTime() - boost::posix_time::seconds(5);
+    // The number of messages to be sent must be greater than 0.
+    uint64_t count;
+    ASSERT_NO_THROW(count = rc1.getOutboundMessageCount());
+    EXPECT_GT(count, 0);
+    // Now, don't specify the rate. In this case the aggressivity dictates
+    // how many messages to send.
+    NakedRateControl rc2(0, 3);
+    rc2.last_sent_ =
+        NakedRateControl::currentTime() - boost::posix_time::seconds(5);
+    ASSERT_NO_THROW(count = rc2.getOutboundMessageCount());
+    EXPECT_EQ(3, count);
+    // Specify the rate and set the timestamp of the last sent message well
+    // to the future. If the resulting due time is well in the future too,
+    // the number of messages to be sent must be 0.
+    NakedRateControl rc3(10, 3);
+    rc3.last_sent_ = NakedRateControl::currentTime() +
+        boost::posix_time::seconds(5);
+    ASSERT_NO_THROW(count = rc3.getOutboundMessageCount());
+    EXPECT_EQ(0, count);
+
+}
+
+// Test the aggressivity modifier for valid and invalid values.
+TEST(RateControl, setAggressivity) {
+    NakedRateControl rc;
+    ASSERT_NO_THROW(rc.setAggressivity(1));
+    EXPECT_THROW(rc.setAggressivity(0), isc::BadValue);
+    EXPECT_THROW(rc.setAggressivity(-1), isc::BadValue);
+}
+
+// Test the rate modifier for valid and invalid rate values.
+TEST(RateControl, setRate) {
+    NakedRateControl rc;
+    EXPECT_NO_THROW(rc.setRate(1));
+    EXPECT_NO_THROW(rc.setRate(0));
+    EXPECT_THROW(rc.setRate(-1), isc::BadValue);
+}
+
+// Test the function which calculates the due time to send next set of
+// messages.
+TEST(RateControl, updateSendDue) {
+    NakedRateControl rc;
+    // Set the send due timestamp to the value which is well in the future.
+    // If we don't hit the due time, the function should not modify the
+    // due time.
+    rc.send_due_ =
+        NakedRateControl::currentTime() + boost::posix_time::seconds(10);
+    boost::posix_time::ptime last_send_due = rc.send_due_;
+    ASSERT_NO_THROW(rc.updateSendDue());
+    EXPECT_TRUE(rc.send_due_ == last_send_due);
+    // Set the due time to the value which is already behind.
+    rc.send_due_ =
+        NakedRateControl::currentTime() - boost::posix_time::seconds(10);
+    last_send_due = rc.send_due_;
+    ASSERT_NO_THROW(rc.updateSendDue());
+    // The value should be modified to the new value.
+    EXPECT_TRUE(rc.send_due_ > last_send_due);
+
+}
+
+// Test that the message send time is updated to the current time.
+TEST(RateControl, updateSendTime) {
+    NakedRateControl rc;
+    // Set the timestamp to the future.
+    rc.last_sent_ =
+        NakedRateControl::currentTime() + boost::posix_time::seconds(5);
+    rc.updateSendTime();
+    // Updated timestamp should be set to now or to the past.
+    EXPECT_TRUE(rc.last_sent_ <= NakedRateControl::currentTime());
+
+}

+ 20 - 0
tests/tools/perfdhcp/tests/stats_mgr_unittest.cc

@@ -187,6 +187,8 @@ TEST_F(StatsMgrTest, Exchange) {
                                                       common_transid));
                                                       common_transid));
     // This is expected to throw because XCHG_DO was not yet
     // This is expected to throw because XCHG_DO was not yet
     // added to Stats Manager for tracking.
     // added to Stats Manager for tracking.
+    ASSERT_FALSE(stats_mgr->hasExchangeStats(StatsMgr4::XCHG_DO));
+    ASSERT_FALSE(stats_mgr->hasExchangeStats(StatsMgr4::XCHG_RA));
     EXPECT_THROW(
     EXPECT_THROW(
         stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet),
         stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet),
         BadValue
         BadValue
@@ -196,8 +198,11 @@ TEST_F(StatsMgrTest, Exchange) {
         BadValue
         BadValue
     );
     );
 
 
+
     // Adding DISCOVER-OFFER exchanges to be tracked by Stats Manager.
     // Adding DISCOVER-OFFER exchanges to be tracked by Stats Manager.
     stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
     stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
+    ASSERT_TRUE(stats_mgr->hasExchangeStats(StatsMgr4::XCHG_DO));
+    ASSERT_FALSE(stats_mgr->hasExchangeStats(StatsMgr4::XCHG_RA));
     // The following two attempts are expected to throw because
     // The following two attempts are expected to throw because
     // invalid exchange types are passed (XCHG_RA instead of XCHG_DO)
     // invalid exchange types are passed (XCHG_RA instead of XCHG_DO)
     EXPECT_THROW(
     EXPECT_THROW(
@@ -259,6 +264,21 @@ TEST_F(StatsMgrTest, MultipleExchanges) {
               stats_mgr->getRcvdPacketsNum(StatsMgr6::XCHG_RR));
               stats_mgr->getRcvdPacketsNum(StatsMgr6::XCHG_RR));
 }
 }
 
 
+TEST_F(StatsMgrTest, ExchangeToString) {
+    // Test DHCPv4 specific exchange names.
+    EXPECT_EQ("DISCOVER-OFFER",
+              StatsMgr4::exchangeToString(StatsMgr4::XCHG_DO));
+    EXPECT_EQ("REQUEST-ACK", StatsMgr4::exchangeToString(StatsMgr4::XCHG_RA));
+
+    // Test DHCPv6 specific exchange names.
+    EXPECT_EQ("SOLICIT-ADVERTISE",
+              StatsMgr6::exchangeToString(StatsMgr6::XCHG_SA));
+    EXPECT_EQ("REQUEST-REPLY", StatsMgr6::exchangeToString(StatsMgr6::XCHG_RR));
+    EXPECT_EQ("RENEW-REPLY", StatsMgr6::exchangeToString(StatsMgr6::XCHG_RN));
+    EXPECT_EQ("RELEASE-REPLY", StatsMgr6::exchangeToString(StatsMgr6::XCHG_RL));
+
+}
+
 TEST_F(StatsMgrTest, SendReceiveSimple) {
 TEST_F(StatsMgrTest, SendReceiveSimple) {
     boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
     boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
     boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER,
     boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER,

+ 365 - 143
tests/tools/perfdhcp/tests/test_control_unittest.cc

@@ -74,8 +74,32 @@ public:
         uint32_t transid_; ///< Last generated transaction id.
         uint32_t transid_; ///< Last generated transaction id.
     };
     };
 
 
+    /// \brief Sets the due times for sedning Solicit, Renew and Release.
+    ///
+    /// There are three class members that hold the due time for sending DHCP
+    /// messages:
+    /// - send_due_ - due time to send Solicit,
+    /// - renew_due_ - due time to send Renew,
+    /// - release_due_ - due time to send Release.
+    /// Some tests in this test suite need to modify these values relative to
+    /// the current time. This function modifies this values using time
+    /// offset values (positive or negative) specified as a difference in
+    /// seconds between current time and the due time.
+    ///
+    /// \param send_secs An offset of the due time for Solicit.
+    /// \param renew_secs An offset of the due time for Renew.
+    /// \param release_secs An offset of the due time for Release.
+    void setRelativeDueTimes(const int send_secs, const int renew_secs = 0,
+                             const int release_secs = 0) {
+        ptime now = microsec_clock::universal_time();
+        basic_rate_control_.setRelativeDue(send_secs);
+        renew_rate_control_.setRelativeDue(renew_secs);
+        release_rate_control_.setRelativeDue(release_secs);
+
+    }
+
     using TestControl::checkExitConditions;
     using TestControl::checkExitConditions;
-    using TestControl::createRenew;
+    using TestControl::createMessageFromReply;
     using TestControl::factoryElapsedTime6;
     using TestControl::factoryElapsedTime6;
     using TestControl::factoryGeneric;
     using TestControl::factoryGeneric;
     using TestControl::factoryIana6;
     using TestControl::factoryIana6;
@@ -84,7 +108,7 @@ public:
     using TestControl::factoryRequestList4;
     using TestControl::factoryRequestList4;
     using TestControl::generateDuid;
     using TestControl::generateDuid;
     using TestControl::generateMacAddress;
     using TestControl::generateMacAddress;
-    using TestControl::getNextExchangesNum;
+    using TestControl::getCurrentTimeout;
     using TestControl::getTemplateBuffer;
     using TestControl::getTemplateBuffer;
     using TestControl::initPacketTemplates;
     using TestControl::initPacketTemplates;
     using TestControl::initializeStatsMgr;
     using TestControl::initializeStatsMgr;
@@ -92,13 +116,22 @@ public:
     using TestControl::processReceivedPacket4;
     using TestControl::processReceivedPacket4;
     using TestControl::processReceivedPacket6;
     using TestControl::processReceivedPacket6;
     using TestControl::registerOptionFactories;
     using TestControl::registerOptionFactories;
+    using TestControl::reset;
     using TestControl::sendDiscover4;
     using TestControl::sendDiscover4;
     using TestControl::sendPackets;
     using TestControl::sendPackets;
-    using TestControl::sendRenewPackets;
+    using TestControl::sendMultipleMessages6;
     using TestControl::sendRequest6;
     using TestControl::sendRequest6;
     using TestControl::sendSolicit6;
     using TestControl::sendSolicit6;
     using TestControl::setDefaults4;
     using TestControl::setDefaults4;
     using TestControl::setDefaults6;
     using TestControl::setDefaults6;
+    using TestControl::basic_rate_control_;
+    using TestControl::renew_rate_control_;
+    using TestControl::release_rate_control_;
+    using TestControl::last_report_;
+    using TestControl::transid_gen_;
+    using TestControl::macaddr_gen_;
+    using TestControl::first_packet_serverid_;
+    using TestControl::interrupted_;
 
 
     NakedTestControl() : TestControl() {
     NakedTestControl() : TestControl() {
         uint32_t clients_num = CommandOptions::instance().getClientsNum() == 0 ?
         uint32_t clients_num = CommandOptions::instance().getClientsNum() == 0 ?
@@ -626,6 +659,167 @@ public:
         }
         }
     }
     }
 
 
+    /// \brief Test that the DHCPv4 Release or Renew message is created
+    /// correctly and comprises expected options.
+    ///
+    /// \param msg_type A type of the message to be tested: DHCPV6_RELEASE
+    /// or DHCPV6_RENEW.
+    void testCreateRenewRelease(const uint16_t msg_type) {
+        // This command line specifies that the Release/Renew messages should
+        // be sent with the same rate as the Solicit messages.
+        std::ostringstream s;
+        s << "perfdhcp -6 -l lo -r 10 ";
+        s << (msg_type == DHCPV6_RELEASE ? "-F" : "-f") << " 10 ";
+        s << "-R 10 -L 10547 -n 10 -e address-and-prefix ::1";
+        ASSERT_NO_THROW(processCmdLine(s.str()));
+        // Create a test controller class.
+        NakedTestControl tc;
+        // Set the transaction id generator which will be used by the
+        // createRenew or createRelease function to generate transaction id.
+        boost::shared_ptr<NakedTestControl::IncrementalGenerator>
+            generator(new NakedTestControl::IncrementalGenerator());
+        tc.setTransidGenerator(generator);
+
+        // Create a Reply packet. The createRelease or createReply function will
+        // need Reply packet to create a corresponding Release or Reply.
+        Pkt6Ptr reply = createReplyPkt6(1);
+
+        Pkt6Ptr msg;
+        // Check that the message is created.
+        ASSERT_NO_THROW(msg = tc.createMessageFromReply(msg_type, reply));
+
+        ASSERT_TRUE(msg);
+        // Check that the message type and transaction id is correct.
+        EXPECT_EQ(msg_type, msg->getType());
+        EXPECT_EQ(1, msg->getTransid());
+
+        // Check that the message has expected options. These are the same for
+        // Release and Renew.
+
+        // Client Identifier.
+        OptionPtr opt_clientid = msg->getOption(D6O_CLIENTID);
+        ASSERT_TRUE(opt_clientid);
+        EXPECT_TRUE(reply->getOption(D6O_CLIENTID)->getData() ==
+                    opt_clientid->getData());
+
+        // Server identifier
+        OptionPtr opt_serverid = msg->getOption(D6O_SERVERID);
+        ASSERT_TRUE(opt_serverid);
+        EXPECT_TRUE(reply->getOption(D6O_SERVERID)->getData() ==
+                opt_serverid->getData());
+
+        // IA_NA
+        OptionPtr opt_ia_na = msg->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 = msg->getOption(D6O_IA_PD);
+        ASSERT_TRUE(opt_ia_pd);
+        EXPECT_TRUE(reply->getOption(D6O_IA_PD)->getData() ==
+                    opt_ia_pd->getData());
+
+        // Make sure that exception is thrown if the Reply message is NULL.
+        EXPECT_THROW(tc.createMessageFromReply(msg_type, Pkt6Ptr()),
+                     isc::BadValue);
+
+    }
+
+    /// \brief Test sending DHCPv6 Releases or Renews.
+    ///
+    /// This function simulates acquiring 10 leases from the server. Returned
+    /// Reply messages are cached and used to send Renew or Release messages.
+    /// The maxmimal number of Renew or Release messages which can be sent is
+    /// equal to the number of leases acquired (10). This function also checks
+    /// that an attempt to send more Renew or Release messages than the number
+    /// of leases acquired will fail.
+    ///
+    /// \param msg_type A type of the message which is simulated to be sent
+    /// (DHCPV6_RENEW or DHCPV6_RELEASE).
+    void testSendRenewRelease(const uint16_t msg_type) {
+        std::string loopback_iface(getLocalLoopback());
+        if (loopback_iface.empty()) {
+            std::cout << "Skipping the test because loopback interface could"
+                " not be detected" << std::endl;
+            return;
+        }
+        // Build a command line. Depending on the message type, we will use
+        // -f<renew-rate> or -F<release-rate> parameter.
+        std::ostringstream s;
+        s << "perfdhcp -6 -l " << loopback_iface << " -r 10 ";
+        s << (msg_type == DHCPV6_RENEW ? "-f" : "-F");
+        s << " 10 -R 10 -L 10547 -n 10 ::1";
+        ASSERT_NO_THROW(processCmdLine(s.str()));
+        // 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 msg_num;
+        // Try to send 5 messages. It should be successful because 10 Reply
+        // messages has been received. For each of them we should be able to
+        // send Renew or Release.
+        ASSERT_NO_THROW(
+            msg_num = tc.sendMultipleMessages6(sock, msg_type, 5)
+        );
+        // Make sure that we have sent 5 messages.
+        EXPECT_EQ(5, msg_num);
+
+        // Try to do it again. We should still have 5 Reply packets for
+        // which Renews or Releases haven't been sent yet.
+        ASSERT_NO_THROW(
+            msg_num = tc.sendMultipleMessages6(sock, msg_type, 5)
+        );
+        EXPECT_EQ(5, msg_num);
+
+        // We used all the Reply packets (we sent Renew or Release for each of
+        // them already). Therefore, no further Renew or Release messages should
+        // be sent before we acquire new leases.
+        ASSERT_NO_THROW(
+            msg_num = tc.sendMultipleMessages6(sock, msg_type, 5)
+        );
+        // Make sure that no message has been sent.
+        EXPECT_EQ(0, msg_num);
+
+    }
+
     /// \brief Parse command line string with CommandOptions.
     /// \brief Parse command line string with CommandOptions.
     ///
     ///
     /// \param cmdline command line string to be parsed.
     /// \param cmdline command line string to be parsed.
@@ -710,6 +904,25 @@ public:
 
 
 };
 };
 
 
+// This test verifies that the class members are reset to expected values.
+TEST_F(TestControlTest, reset) {
+    ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l ethx -r 50 -f 30 -F 10 -a 3 all"));
+    NakedTestControl tc;
+    tc.reset();
+    EXPECT_EQ(3, tc.basic_rate_control_.getAggressivity());
+    EXPECT_EQ(3, tc.renew_rate_control_.getAggressivity());
+    EXPECT_EQ(3, tc.release_rate_control_.getAggressivity());
+    EXPECT_EQ(50, tc.basic_rate_control_.getRate());
+    EXPECT_EQ(30, tc.renew_rate_control_.getRate());
+    EXPECT_EQ(10, tc.release_rate_control_.getRate());
+    EXPECT_FALSE(tc.last_report_.is_not_a_date_time());
+    EXPECT_FALSE(tc.transid_gen_);
+    EXPECT_FALSE(tc.macaddr_gen_);
+    EXPECT_TRUE(tc.first_packet_serverid_.empty());
+    EXPECT_FALSE(tc.interrupted_);
+
+}
+
 TEST_F(TestControlTest, GenerateDuid) {
 TEST_F(TestControlTest, GenerateDuid) {
     // Simple command line that simulates one client only. Always the
     // Simple command line that simulates one client only. Always the
     // same DUID will be generated.
     // same DUID will be generated.
@@ -1226,157 +1439,166 @@ TEST_F(TestControlTest, PacketTemplates) {
     EXPECT_THROW(tc.initPacketTemplates(), isc::BadValue);
     EXPECT_THROW(tc.initPacketTemplates(), isc::BadValue);
 }
 }
 
 
-TEST_F(TestControlTest, RateControl) {
-    // We don't specify the exchange rate here so the aggressivity
-    // value will determine how many packets are to be send each
-    // time we query the getNextExchangesNum.
-    ASSERT_NO_THROW(processCmdLine("perfdhcp -l 127.0.0.1 all"));
-    CommandOptions& options = CommandOptions::instance();
+TEST_F(TestControlTest, processRenew) {
+    testSendRenewRelease(DHCPV6_RENEW);
+}
 
 
-    NakedTestControl tc1;
-    uint64_t xchgs_num = tc1.getNextExchangesNum(microsec_clock::universal_time(),
-                                                 options.getRate());
-    EXPECT_EQ(options.getAggressivity(), xchgs_num);
+TEST_F(TestControlTest, processRelease) {
+    testSendRenewRelease(DHCPV6_RELEASE);
+}
 
 
-    // The exchange rate is now 1 per second. We don't know how many
-    // exchanges have to initiated exactly but for sure it has to be
-    // non-zero value. Also, since aggressivity is very high we expect
-    // that it will not be restricted by aggressivity.
-    ASSERT_NO_THROW(
-        processCmdLine("perfdhcp -l 127.0.0.1 -a 1000000 -r 1 all")
-    );
-    NakedTestControl tc2;
-    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.
+// This test verifies that the DHCPV6 Renew message is created correctly
+// and that it comprises all required options.
+TEST_F(TestControlTest, createRenew) {
+    testCreateRenewRelease(DHCPV6_RENEW);
 }
 }
 
 
-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.
+// This test verifies that the DHCPv6 Release message is created correctly
+// and that it comprises all required options.
+TEST_F(TestControlTest, createRelease) {
+    testCreateRenewRelease(DHCPV6_RELEASE);
+}
+
+// This test verifies that the current timeout value for waiting for
+// the server's responses is valid. The timeout value corresponds to the
+// time period between now and the next message to be sent from the
+// perfdhcp to a server.
+TEST_F(TestControlTest, getCurrentTimeout) {
+    // Process the command line: set the rate for Discovers to 10,
+    // and set Renew rate to 0 (-f flag absent).
+    ASSERT_NO_THROW(processCmdLine("perfdhcp -4 -l lo -r 10 ::1"));
     NakedTestControl tc;
     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));
-    }
+    // Make sure that the renew rate is 0.
+    ASSERT_EQ(0, CommandOptions::instance().getRenewRate());
+    // Simulate the case when we are already behind the due time for
+    // the next Discover to be sent.
+    tc.setRelativeDueTimes(-3);
+    // Expected timeout value is 0, which means that perfdhcp should
+    // not wait for server's response but rather send the next
+    // message to a server immediately.
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+    // Now, let's do set the due time to a value in the future. The returned
+    // timeout value should be somewhere between now and this time in the
+    // future. The value of ten seconds ahead should be safe and guarantee
+    // that the returned timeout value is non-zero, even though there is a
+    // delay between setting the send_due_ value and invoking the function.
+    tc.setRelativeDueTimes(10);
+    uint32_t timeout = tc.getCurrentTimeout();
+    EXPECT_GT(timeout, 0);
+    EXPECT_LE(timeout, 10000000);
+}
 
 
-    // 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));
-    }
+// This test verifies that the current timeout value for waiting for the
+// server's responses is valid. In this case, we are simulating that perfdhcp
+// sends Renew requests to the server, apart from the regular 4-way exchanges.
+// The timeout value depends on both the due time to send next Solicit and the
+// due time to send Renew - the timeout should be ajusted to the due time that
+// occurs sooner.
+TEST_F(TestControlTest, getCurrentTimeoutRenew) {
+    // Set the Solicit rate to 10 and the Renew rate 5.
+    ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l lo -r 10 -f 5 ::1"));
+    NakedTestControl tc;
+
+    // Make sure, that the Renew rate has been set to 5.
+    ASSERT_EQ(5, CommandOptions::instance().getRenewRate());
+    // The send_due_ is in the past, the renew_due_ is in the future.
+    tc.setRelativeDueTimes(-3, 3);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    // Swap the due times from the previous check. The effect should be the
+    // same.
+    tc.setRelativeDueTimes(3, -3);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    // Set both due times to the future. The renew due time is to occur
+    // sooner. The timeout should be a value between now and the
+    // renew due time.
+    tc.setRelativeDueTimes(10, 5);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 5000000);
+
+    // Repeat the same check, but swap the due times.
+    tc.setRelativeDueTimes(5, 10);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 5000000);
 
 
-    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.
+// This test verifies that the current timeout value for waiting for the
+// server's responses is valid. In this case, we are simulating that perfdhcp
+// sends Release requests to the server, apart from the regular 4-way exchanges.
+TEST_F(TestControlTest, getCurrentTimeoutRelease) {
+    // Set the Solicit rate to 10 and the Release rate 5.
+    ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l lo -r 10 -F 5 ::1"));
     NakedTestControl tc;
     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());
+    // Make sure, that the Release rate has been set to 5.
+    ASSERT_EQ(5, CommandOptions::instance().getReleaseRate());
+    // The send_due_ is in the past, the renew_due_ is in the future.
+    tc.setRelativeDueTimes(-3, 0, 3);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    // Swap the due times from the previous check. The effect should be the
+    // same.
+    tc.setRelativeDueTimes(3, 0, -3);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    // Set both due times to the future. The renew due time is to occur
+    // sooner. The timeout should be a value between now and the
+    // release due time.
+    tc.setRelativeDueTimes(10, 0, 5);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 5000000);
+
+    // Repeat the same check, but swap the due times.
+    tc.setRelativeDueTimes(5, 0, 10);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 5000000);
 
 
 }
 }
 
 
+// This test verifies that the current timeout value for waiting for the
+// server's responses is valid. In this case, we are simulating that perfdhcp
+// sends both Renew and Release requests to the server, apart from the regular
+// 4-way exchanges.
+TEST_F(TestControlTest, getCurrentTimeoutRenewRelease) {
+    // Set the Solicit rate to 10 and, Renew rate to 5, Release rate to 3.
+    ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l lo -r 10 -f 5 -F 3 ::1"));
+    NakedTestControl tc;
+
+    // Make sure the Renew and Release rates has been set to a non-zero value.
+    ASSERT_EQ(5, CommandOptions::instance().getRenewRate());
+    ASSERT_EQ(3, CommandOptions::instance().getReleaseRate());
+
+    // If any of the due times is in the past, the timeout value should be 0,
+    // to indicate that the next message should be sent immediately.
+    tc.setRelativeDueTimes(-3, 3, 5);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    tc.setRelativeDueTimes(-3, 5, 3);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    tc.setRelativeDueTimes(3, -3, 5);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    tc.setRelativeDueTimes(3, 2, -5);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    tc.setRelativeDueTimes(-3, -2, -5);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    // If due times are in the future, the timeout value should be aligned to
+    // the due time which occurs the soonest.
+    tc.setRelativeDueTimes(10, 9, 8);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 8000000);
+
+    tc.setRelativeDueTimes(10, 8, 9);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 8000000);
+
+    tc.setRelativeDueTimes(5, 8, 9);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 5000000);
+
+}