Parcourir la source

[master] Merge branch 'trac4254'

Marcin Siodelski il y a 9 ans
Parent
commit
8c5630b9ce

+ 6 - 8
src/bin/perfdhcp/command_options.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -691,8 +691,6 @@ CommandOptions::validate() const {
           "-B is not compatible with IPv6 (-6)");
     check((getIpVersion() != 6) && (isRapidCommit() != 0),
           "-6 (IPv6) must be set to use -c");
-    check((getIpVersion() != 6) && (getRenewRate() !=0),
-          "-f<renew-rate> may be used with -6 (IPv6) only");
     check((getIpVersion() != 6) && (getReleaseRate() != 0),
           "-F<release-rate> may be used with -6 (IPv6) only");
     check((getExchangeMode() == DO_SA) && (getNumRequests().size() > 1),
@@ -963,6 +961,11 @@ CommandOptions::usage() const {
         "-E<time-offset>: Offset of the (DHCPv4) secs field / (DHCPv6)\n"
         "    elapsed-time option in the (second/request) template.\n"
         "    The value 0 disables it.\n"
+        "-f<renew-rate>: Rate at which DHCPv4 or DHCPv6 renew requests are sent\n"
+        "    to a server. This value is only valid when used in conjunction\n"
+        "    with 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"
         "-h: Print this help.\n"
         "-i: Do only the initial part of an exchange: DO or SA, depending on\n"
         "    whether -6 is given.\n"
@@ -1010,11 +1013,6 @@ CommandOptions::usage() const {
         "\n"
         "DHCPv6 only options:\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"

+ 17 - 17
src/bin/perfdhcp/perfdhcp.xml

@@ -319,6 +319,23 @@
             </varlistentry>
 
             <varlistentry>
+              <term><option>-f <replaceable class="parameter">renew-rate</replaceable></option></term>
+              <listitem>
+                <para>
+                    Rate at which DHCPv4 or DHCPv6 renew requests are sent
+                    to a server. This value is only valid when used in conjunction
+                    with the exchange rate (given by <option>-r <replaceable
+                    class="parameter">rate</replaceable></option>).
+                    Furthermore the sum of this value and the release-rate
+                    (given by <option>-F <replaceable class="parameter">
+                    rate</replaceable></option>) must be equal to or less than
+                    the exchange rate.
+                </para>
+              </listitem>
+            </varlistentry>
+
+
+            <varlistentry>
                 <term><option>-h</option></term>
                 <listitem>
                     <para>
@@ -561,23 +578,6 @@
                 </varlistentry>
 
                 <varlistentry>
-                    <term><option>-f <replaceable class="parameter">renew-rate</replaceable></option></term>
-                    <listitem>
-                        <para>
-                            Rate at which IPv6 RENEW requests are sent
-                            to a server. This value is only valid when
-                            used in conjunction with the exchange
-                            rate (given by <option>-r <replaceable
-                            class="parameter">rate</replaceable></option>).
-                            Furthermore the sum of this value and the
-                            release-rate (given by <option>-F <replaceable
-                            class="parameter">rate</replaceable></option>)
-                            must be equal to or less than the exchange rate.
-                        </para>
-                    </listitem>
-                </varlistentry>
-
-                <varlistentry>
                     <term><option>-F <replaceable class="parameter">release-rate</replaceable></option></term>
                     <listitem>
                         <para>

+ 57 - 36
src/bin/perfdhcp/stats_mgr.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -22,6 +22,7 @@
 
 #include <iostream>
 #include <map>
+#include <queue>
 
 
 namespace isc {
@@ -114,6 +115,7 @@ public:
     enum ExchangeType {
         XCHG_DO,  ///< DHCPv4 DISCOVER-OFFER
         XCHG_RA,  ///< DHCPv4 REQUEST-ACK
+        XCHG_RNA, ///< DHCPv4 REQUEST-ACK (renewal)
         XCHG_SA,  ///< DHCPv6 SOLICIT-ADVERTISE
         XCHG_RR,  ///< DHCPv6 REQUEST-REPLY
         XCHG_RN,  ///< DHCPv6 RENEW-REPLY
@@ -172,7 +174,7 @@ public:
         /// 1023 values maximum. Search operation on this index generally
         /// returns the range of packets that have the same transaction id
         /// hash assigned but most often these ranges will be short so further
-        /// search within a range to find a packet with pacrticular transaction
+        /// search within a range to find a packet with particular transaction
         /// id will not be intensive.
         ///
         /// Example 1: Add elements to the list
@@ -188,7 +190,7 @@ public:
         /// packets_collection.template get<0>().push_back(pkt2);
         /// \endcode
         ///
-        /// Example 2: Access elements through sequencial index
+        /// Example 2: Access elements through sequential index
         /// \code
         /// PktList packets_collection();
         /// ...  # Add elements to the container
@@ -241,7 +243,7 @@ public:
             >
         > PktList;
 
-        /// Packet list iterator for sequencial access to elements.
+        /// Packet list iterator for sequential access to elements.
         typedef typename PktList::iterator PktListIterator;
         /// Packet list index to search packets using transaction id hash.
         typedef typename PktList::template nth_index<1>::type
@@ -249,6 +251,9 @@ public:
         /// Packet list iterator to access packets using transaction id hash.
         typedef typename PktListTransidHashIndex::const_iterator
             PktListTransidHashIterator;
+        /// Packet list iterator queue for removal.
+        typedef typename std::queue<PktListTransidHashIterator>
+            PktListRemovalQueue;
 
         /// \brief Constructor
         ///
@@ -419,7 +424,7 @@ public:
                 // take a little more expensive approach to look packets using
                 // alternative index (transaction id & 1023).
                 PktListTransidHashIndex& idx = sent_packets_.template get<1>();
-                // Packets are grouped using trasaction id masked with value
+                // Packets are grouped using transaction id masked with value
                 // of 1023. For instance, packets with transaction id equal to
                 // 1, 1024 ... will belong to the same group (a.k.a. bucket).
                 // When using alternative index we don't find the packet but
@@ -437,6 +442,8 @@ public:
                 // bucket size the better. If bucket sizes appear to big we
                 // might want to increase number of buckets.
                 unordered_lookup_size_sum_ += std::distance(p.first, p.second);
+                // Removal can be done only after the loop
+                PktListRemovalQueue to_remove;
                 for (PktListTransidHashIterator it = p.first; it != p.second;
                      ++it) {
                     if ((*it)->getTransid() == rcvd_packet->getTransid()) {
@@ -454,19 +461,30 @@ public:
                             (static_cast<double>(packet_period.length().fractional_seconds())
                              / packet_period.length().ticks_per_second());
                         if (drop_time_ > 0 && (period_fractional > drop_time_)) {
-                            // The packet pointed to by 'it' is timed out so we
-                            // have to remove it. Removal may invalidate the
-                            // next_sent_ pointer if it points to the packet
-                            // being removed. So, we set the next_sent_ to point
-                            // to the next packet after removed one. This
-                            // pointer will be further updated in the following
-                            // iterations, if the subsequent packets are also
-                            // timed out.
-                            next_sent_ = eraseSent(sent_packets_.template project<0>(it));
-                            ++collected_;
+                            // Push the iterator on the removal queue.
+                            to_remove.push(it);
+
                         }
                     }
                 }
+
+                // Deal with the removal queue
+                while (!to_remove.empty()) {
+                    PktListTransidHashIterator it = to_remove.front();
+                    to_remove.pop();
+                    // The packet pointed to by 'it' is timed out so
+                    // we have to remove it. 
+                    if (packet_found || !to_remove.empty()) {
+                        eraseSent(sent_packets_.template project<0>(it));
+                    } else {
+                        // Removal may invalidate the next_sent_
+                        // pointer if it points to the packet being
+                        // removed. So, we set the next_sent_ to point
+                        // to the next packet after removed one.
+                        next_sent_ = eraseSent(sent_packets_.template project<0>(it));
+                    }
+                    ++collected_;
+                }
             }
 
             if (!packet_found) {
@@ -487,14 +505,14 @@ public:
             return(sent_packet);
         }
 
-        /// \brief Return minumum delay between sent and received packet.
+        /// \brief Return minimum delay between sent and received packet.
         ///
         /// Method returns minimum delay between sent and received packet.
         ///
         /// \return minimum delay between packets.
         double getMinDelay() const { return(min_delay_); }
 
-        /// \brief Return maxmimum delay between sent and received packet.
+        /// \brief Return maximum delay between sent and received packet.
         ///
         /// Method returns maximum delay between sent and received packet.
         ///
@@ -535,13 +553,13 @@ public:
                         getAvgDelay() * getAvgDelay()));
         }
 
-        /// \brief Return number of orphant packets.
+        /// \brief Return number of orphan packets.
         ///
         /// Method returns number of received packets that had no matching
         /// sent packet. It is possible that such packet was late or not
         /// for us.
         ///
-        /// \return number of orphant received packets.
+        /// \return number of orphan received packets.
         uint64_t getOrphans() const { return(orphans_); }
 
         /// \brief Return number of garbage collected packets.
@@ -631,7 +649,8 @@ public:
         /// 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.
+        /// not identified as a response to any of the messages sent by
+        /// perfdhcp.
         void printMainStats() const {
             using namespace std;
             cout << "sent packets: " << getSentPacketsNum() << endl
@@ -752,7 +771,7 @@ public:
                  // when test is completed.
                  archived_packets_.push_back(*it);
              }
-             // get<0>() template returns sequencial index to
+             // get<0>() template returns sequential index to
              // container.
              return(sent_packets_.template get<0>().erase(it));
         }
@@ -785,7 +804,7 @@ public:
         /// to keep all packets archived throughout the test.
         bool archive_enabled_;
 
-        /// Maxmimum time elapsed between sending and receiving packet
+        /// Maximum time elapsed between sending and receiving packet
         /// before packet is assumed dropped.
         double drop_time_;
 
@@ -796,16 +815,16 @@ public:
         double sum_delay_;             ///< Sum of delays between sent
                                        ///< and received packets.
         double sum_delay_squared_;     ///< Squared sum of delays between
-                                       ///< sent and recived packets.
+                                       ///< sent and received packets.
 
-        uint64_t orphans_;   ///< Number of orphant received packets.
+        uint64_t orphans_;   ///< Number of orphan received packets.
 
         uint64_t collected_; ///< Number of garbage collected packets.
 
         /// Sum of unordered lookup sets. Needed to calculate mean size of
         /// lookup set. It is desired that number of unordered lookups is
         /// minimal for performance reasons. Tracking number of lookups and
-        /// mean size of the lookup set should give idea of packets serach
+        /// mean size of the lookup set should give idea of packets search
         /// complexity.
         uint64_t unordered_lookup_size_sum_;
 
@@ -823,7 +842,7 @@ public:
     typedef boost::shared_ptr<ExchangeStats> ExchangeStatsPtr;
     /// Map containing all specified exchange types.
     typedef typename std::map<ExchangeType, ExchangeStatsPtr> ExchangesMap;
-    /// Iterator poiting to \ref ExchangesMap
+    /// Iterator pointing to \ref ExchangesMap
     typedef typename ExchangesMap::const_iterator ExchangesMapIterator;
     /// Map containing custom counters.
     typedef typename std::map<std::string, CustomCounterPtr> CustomCountersMap;
@@ -876,7 +895,7 @@ public:
     /// 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
+    /// \param xchg_type A type of the exchange being represented by the
     /// \ref ExchangeStats object.
     ///
     /// \return true if the \ref ExchangeStats object has been added for a
@@ -902,9 +921,9 @@ public:
             CustomCounterPtr(new CustomCounter(long_name));
     }
 
-    /// \brief Check if any packet drops occured.
+    /// \brief Check if any packet drops occurred.
     ///
-    // \return true, if packet drops occured.
+    // \return true, if packet drops occurred.
     bool droppedPackets() const {
         for (ExchangesMapIterator it = exchanges_.begin();
              it != exchanges_.end();
@@ -920,7 +939,7 @@ public:
     ///
     /// Method returns specified counter.
     ///
-    /// \param counter_key key poiting to the counter in the counters map.
+    /// \param counter_key key pointing to the counter in the counters map.
     /// The short counter name has to be used to access counter.
     /// \return pointer to specified counter object.
     CustomCounterPtr getCounter(const std::string& counter_key) {
@@ -936,7 +955,7 @@ public:
     ///
     /// Increment counter value by one.
     ///
-    /// \param counter_key key poiting to the counter in the counters map.
+    /// \param counter_key key pointing to the counter in the counters map.
     /// \param value value to increment counter by.
     /// \return pointer to specified counter after incrementation.
     const CustomCounter& incrementCounter(const std::string& counter_key,
@@ -991,7 +1010,7 @@ public:
         return(sent_packet);
     }
 
-    /// \brief Return minumum delay between sent and received packet.
+    /// \brief Return minimum delay between sent and received packet.
     ///
     /// Method returns minimum delay between sent and received packet
     /// for specified exchange type.
@@ -1004,7 +1023,7 @@ public:
         return(xchg_stats->getMinDelay());
     }
 
-    /// \brief Return maxmimum delay between sent and received packet.
+    /// \brief Return maximum delay between sent and received packet.
     ///
     /// Method returns maximum delay between sent and received packet
     /// for specified exchange type.
@@ -1039,14 +1058,14 @@ public:
         return(xchg_stats->getStdDevDelay());
     }
 
-    /// \brief Return number of orphant packets.
+    /// \brief Return number of orphan packets.
     ///
-    /// Method returns number of orphant packets for specified
+    /// Method returns number of orphan packets for specified
     /// exchange type.
     ///
     /// \param xchg_type exchange type.
     /// \throw isc::BadValue if invalid exchange type specified.
-    /// \return number of orphant packets so far.
+    /// \return number of orphan packets so far.
     uint64_t getOrphans(const ExchangeType xchg_type) const {
         ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
         return(xchg_stats->getOrphans());
@@ -1179,6 +1198,8 @@ public:
             return("DISCOVER-OFFER");
         case XCHG_RA:
             return("REQUEST-ACK");
+        case XCHG_RNA:
+            return("REQUEST-ACK (renewal)");
         case XCHG_SA:
             return("SOLICIT-ADVERTISE");
         case XCHG_RR:

+ 113 - 17
src/bin/perfdhcp/test_control.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -315,6 +315,22 @@ TestControl::checkExitConditions() const {
     return (false);
 }
 
+Pkt4Ptr
+TestControl::createRequestFromAck(const dhcp::Pkt4Ptr& ack) {
+    if (!ack) {
+        isc_throw(isc::BadValue, "Unable to create DHCPREQUEST from a"
+                  " null DHCPACK message");
+    } else if (ack->getYiaddr().isV4Zero()) {
+        isc_throw(isc::BadValue, "Unable to create DHCPREQUEST from a"
+                  " DHCPACK message containing yiaddr of 0");
+    }
+    Pkt4Ptr msg(new Pkt4(DHCPREQUEST, generateTransid()));
+    msg->setCiaddr(ack->getYiaddr());
+    msg->setHWAddr(ack->getHWAddr());
+    msg->addOption(generateClientId(msg->getHWAddr()));
+    return (msg);
+}
+
 Pkt6Ptr
 TestControl::createMessageFromReply(const uint16_t msg_type,
                                     const dhcp::Pkt6Ptr& reply) {
@@ -481,6 +497,15 @@ TestControl::generateMacAddress(uint8_t& randomized) const {
     return (mac_addr);
 }
 
+OptionPtr
+TestControl::generateClientId(const dhcp::HWAddrPtr& hwaddr) const {
+    std::vector<uint8_t> client_id(1, static_cast<uint8_t>(hwaddr->htype_));
+    client_id.insert(client_id.end(), hwaddr->hwaddr_.begin(),
+                     hwaddr->hwaddr_.end());
+    return (OptionPtr(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
+                                 client_id)));
+}
+
 std::vector<uint8_t>
 TestControl::generateDuid(uint8_t& randomized) const {
     CommandOptions& options = CommandOptions::instance();
@@ -663,6 +688,9 @@ TestControl::initializeStatsMgr() {
             stats_mgr4_->addExchangeStats(StatsMgr4::XCHG_RA,
                                           options.getDropTime()[1]);
         }
+        if (options.getRenewRate() != 0) {
+            stats_mgr4_->addExchangeStats(StatsMgr4::XCHG_RNA);
+        }
 
     } else if (options.getIpVersion() == 6) {
         stats_mgr6_.reset();
@@ -725,7 +753,7 @@ TestControl::openSocket() const {
     // Local name is specified along with '-l' option.
     // It may point to interface name or local address.
     if (!localname.empty()) {
-        // CommandOptions should be already aware wether local name
+        // CommandOptions should be already aware whether local name
         // is interface name or address because it uses IfaceMgr to
         // scan interfaces and get's their names.
         if (options.isInterface()) {
@@ -798,7 +826,7 @@ TestControl::sendPackets(const TestControlSocket& socket,
     for (uint64_t i = packets_num; i > 0; --i) {
         if (options.getIpVersion() == 4) {
             // No template packets means that no -T option was specified.
-            // We have to build packets ourselfs.
+            // We have to build packets ourselves.
             if (template_buffers_.empty()) {
                 sendDiscover4(socket, preload);
             } else {
@@ -808,7 +836,7 @@ TestControl::sendPackets(const TestControlSocket& socket,
             }
         } else {
             // No template packets means that no -T option was specified.
-            // We have to build packets ourselfs.
+            // We have to build packets ourselves.
             if (template_buffers_.empty()) {
                 sendSolicit6(socket, preload);
             } else {
@@ -832,6 +860,17 @@ TestControl::sendPackets(const TestControlSocket& socket,
 }
 
 uint64_t
+TestControl::sendMultipleRequests(const TestControlSocket& socket,
+                                  const uint64_t msg_num) {
+    for (uint64_t i = 0; i < msg_num; ++i) {
+        if (!sendRequestFromAck(socket)) {
+            return (i);
+        }
+    }
+    return (msg_num);
+}
+
+uint64_t
 TestControl::sendMultipleMessages6(const TestControlSocket& socket,
                                    const uint32_t msg_type,
                                    const uint64_t msg_num) {
@@ -1072,7 +1111,28 @@ TestControl::processReceivedPacket4(const TestControlSocket& socket,
             }
         }
     } else if (pkt4->getType() == DHCPACK) {
-        stats_mgr4_->passRcvdPacket(StatsMgr4::XCHG_RA, pkt4);
+        // If received message is DHCPACK, we have to check if this is
+        // a response to 4-way exchange. We'll match this packet with
+        // a DHCPREQUEST sent as part of the 4-way exchanges.
+        if (stats_mgr4_->passRcvdPacket(StatsMgr4::XCHG_RA, pkt4)) {
+            // The DHCPACK belongs to DHCPREQUEST-DHCPACK exchange type.
+            // So, we may need to keep this DHCPACK in the storage if renews.
+            // Note that, DHCPACK messages hold the information about
+            // leases assigned. We use this information to renew.
+            if (stats_mgr4_->hasExchangeStats(StatsMgr4::XCHG_RNA)) {
+                // Renew messages are sent, because StatsMgr has the
+                // specific exchange type specified. Let's append the DHCPACK.
+                // message to a storage
+                ack_storage_.append(pkt4);
+            }
+        // The DHCPACK message is not a server's response to the DHCPREQUEST
+        // message sent within the 4-way exchange. It may be a response to a
+        // renewal. In this case 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 DHCPACK.
+        } else if (stats_mgr4_->hasExchangeStats(StatsMgr4::XCHG_RNA)) {
+            stats_mgr4_->passRcvdPacket(StatsMgr4::XCHG_RNA, pkt4);
+        }
     }
 }
 
@@ -1364,12 +1424,17 @@ TestControl::run() {
 
         // If -f<renew-rate> option was specified we have to check how many
         // Renew packets should be sent to catch up with a desired rate.
-        if ((options.getIpVersion() == 6) && (options.getRenewRate() != 0)) {
+        if (options.getRenewRate() != 0) {
             uint64_t renew_packets_due =
                 renew_rate_control_.getOutboundMessageCount();
             checkLateMessages(renew_rate_control_);
-            // Send Renew messages.
-            sendMultipleMessages6(socket, DHCPV6_RENEW, renew_packets_due);
+
+            // Send multiple renews to satisfy the desired rate.
+            if (options.getIpVersion() == 4) {
+                sendMultipleRequests(socket, renew_packets_due);
+            } else {
+                sendMultipleMessages6(socket, DHCPV6_RENEW, renew_packets_due);
+            }
         }
 
         // If -F<release-rate> option was specified we have to check how many
@@ -1389,7 +1454,7 @@ TestControl::run() {
         }
 
         // If we are sending Renews to the server, the Reply packets are cached
-        // so as leases for which we send Renews can be idenitfied. The major
+        // so as leases for which we send Renews can be identified. The major
         // issue with this approach is that most of the time we are caching
         // more packets than we actually need. This function removes excessive
         // Reply messages to reduce the memory and CPU utilization. Note that
@@ -1428,7 +1493,7 @@ TestControl::run() {
     }
 
     int ret_code = 0;
-    // Check if any packet drops occured.
+    // Check if any packet drops occurred.
     if (options.getIpVersion() == 4) {
         ret_code = stats_mgr4_->droppedPackets() ? 3 : 0;
     } else if (options.getIpVersion() == 6)  {
@@ -1479,7 +1544,7 @@ TestControl::sendDiscover4(const TestControlSocket& socket,
     // Generate the MAC address to be passed in the packet.
     uint8_t randomized = 0;
     std::vector<uint8_t> mac_address = generateMacAddress(randomized);
-    // Generate trasnaction id to be set for the new exchange.
+    // Generate transaction id to be set for the new exchange.
     const uint32_t transid = generateTransid();
     Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, transid));
     if (!pkt4) {
@@ -1504,6 +1569,9 @@ TestControl::sendDiscover4(const TestControlSocket& socket,
     // Set hardware address
     pkt4->setHWAddr(HTYPE_ETHER, mac_address.size(), mac_address);
 
+    // Set client identifier
+    pkt4->addOption(generateClientId(pkt4->getHWAddr()));
+
     pkt4->pack();
     IfaceMgr::instance().send(pkt4);
     if (!preload) {
@@ -1521,13 +1589,13 @@ TestControl::sendDiscover4(const TestControlSocket& socket,
                            const std::vector<uint8_t>& template_buf,
                            const bool preload /* = false */) {
     basic_rate_control_.updateSendTime();
-    // Get the first argument if mulitple the same arguments specified
+    // Get the first argument if multiple the same arguments specified
     // in the command line. First one refers to DISCOVER packets.
     const uint8_t arg_idx = 0;
     // Generate the MAC address to be passed in the packet.
     uint8_t randomized = 0;
     std::vector<uint8_t> mac_address = generateMacAddress(randomized);
-    // Generate trasnaction id to be set for the new exchange.
+    // Generate transaction id to be set for the new exchange.
     const uint32_t transid = generateTransid();
     // Get transaction id offset.
     size_t transid_offset = getTransactionIdOffset(arg_idx);
@@ -1569,6 +1637,32 @@ TestControl::sendDiscover4(const TestControlSocket& socket,
 }
 
 bool
+TestControl::sendRequestFromAck(const TestControlSocket& socket) {
+    // Update timestamp of last sent renewal.
+    renew_rate_control_.updateSendTime();
+
+    // Get one of the recorded DHCPACK messages.
+    Pkt4Ptr ack = ack_storage_.getRandom();
+    if (!ack) {
+        return (false);
+    }
+
+    // Create message of the specified type.
+    Pkt4Ptr msg = createRequestFromAck(ack);
+    setDefaults4(socket, msg);
+    msg->pack();
+    // And send it.
+    IfaceMgr::instance().send(msg);
+    if (!stats_mgr4_) {
+        isc_throw(Unexpected, "Statistics Manager for DHCPv4 "
+                  "hasn't been initialized");
+    }
+    stats_mgr4_->passSentPacket(StatsMgr4::XCHG_RNA, msg);
+    return (true);
+}
+
+
+bool
 TestControl::sendMessageFromReply(const uint16_t msg_type,
                                   const TestControlSocket& socket) {
     // We only permit Release or Renew messages to be sent using this function.
@@ -1647,6 +1741,8 @@ TestControl::sendRequest4(const TestControlSocket& socket,
 
     // Set hardware address
     pkt4->setHWAddr(offer_pkt4->getHWAddr());
+    // Set client id.
+    pkt4->addOption(generateClientId(pkt4->getHWAddr()));
     // Set elapsed time.
     uint32_t elapsed_time = getElapsedTime<Pkt4Ptr>(discover_pkt4, offer_pkt4);
     pkt4->setSecs(static_cast<uint16_t>(elapsed_time / 1000));
@@ -1677,7 +1773,7 @@ TestControl::sendRequest4(const TestControlSocket& socket,
     // We need to go back by HW_ETHER_LEN (MAC address length)
     // because this offset points to last octet of MAC address.
     size_t rand_offset = getRandomOffset(arg_idx) - HW_ETHER_LEN + 1;
-    // Create temporaru buffer from the template.
+    // Create temporary buffer from the template.
     std::vector<uint8_t> in_buf(template_buf.begin(),
                                 template_buf.end());
     // Check if given randomization offset is not out of bounds.
@@ -1946,7 +2042,7 @@ TestControl::sendSolicit6(const TestControlSocket& socket,
     // Generate DUID to be passed to the packet
     uint8_t randomized = 0;
     std::vector<uint8_t> duid = generateDuid(randomized);
-    // Generate trasnaction id to be set for the new exchange.
+    // Generate transaction id to be set for the new exchange.
     const uint32_t transid = generateTransid();
     Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, transid));
     if (!pkt6) {
@@ -1995,7 +2091,7 @@ TestControl::sendSolicit6(const TestControlSocket& socket,
     const int arg_idx = 0;
     // Get transaction id offset.
     size_t transid_offset = getTransactionIdOffset(arg_idx);
-    // Generate trasnaction id to be set for the new exchange.
+    // Generate transaction id to be set for the new exchange.
     const uint32_t transid = generateTransid();
     // Create packet.
     PerfPkt6Ptr pkt6(new PerfPkt6(&template_buf[0], template_buf.size(),
@@ -2005,7 +2101,7 @@ TestControl::sendSolicit6(const TestControlSocket& socket,
     }
     size_t rand_offset = getRandomOffset(arg_idx);
     // randomized will pick number of bytes randomized so we can
-    // just use part of the generated duid and substitude a few bytes
+    // just use part of the generated duid and substitute a few bytes
     /// in template.
     uint8_t randomized = 0;
     std::vector<uint8_t> duid = generateDuid(randomized);

+ 48 - 12
src/bin/perfdhcp/test_control.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -124,7 +124,7 @@ public:
     typedef StatsMgr<dhcp::Pkt4> StatsMgr4;
     /// Pointer to Statistics Manager for DHCPv4;
     typedef boost::shared_ptr<StatsMgr4> StatsMgr4Ptr;
-    /// Statictics Manager for DHCPv6.
+    /// Statistics Manager for DHCPv6.
     typedef StatsMgr<dhcp::Pkt6> StatsMgr6;
     /// Pointer to Statistics Manager for DHCPv6.
     typedef boost::shared_ptr<StatsMgr6> StatsMgr6Ptr;
@@ -246,7 +246,7 @@ public:
     /// throw exception.
     ///
     /// \throw isc::InvalidOperation if command line options are not parsed.
-    /// \throw isc::Unexpected if internal Test Controller error occured.
+    /// \throw isc::Unexpected if internal Test Controller error occurred.
     /// \return error_code, 3 if number of received packets is not equal
     /// to number of sent packets, 0 if everything is ok.
     int run();
@@ -306,13 +306,21 @@ protected:
     /// has been reached.
     void cleanCachedPackets();
 
+    /// \brief Creates DHCPREQUEST from a DHCPACK message.
+    ///
+    /// \param ack An instance of the DHCPACK message to be used to
+    /// create a new message.
+    ///
+    /// \return Pointer to the created message.
+    dhcp::Pkt4Ptr createRequestFromAck(const dhcp::Pkt4Ptr& ack);
+
     /// \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 msg_type A type of the message to be created.
     /// \param reply An instance of the Reply packet which contents should
     /// be used to create an instance of the new message.
     ///
@@ -432,6 +440,16 @@ protected:
                                                uint16_t type,
                                                const dhcp::OptionBuffer& buf);
 
+    /// \brief Generate DHCPv4 client identifier from HW address.
+    ///
+    /// This method generates DHCPv4 client identifier option from a
+    /// HW address.
+    ///
+    /// \param hwaddr HW address.
+    ///
+    /// \return Pointer to an instance of the generated option.
+    dhcp::OptionPtr generateClientId(const dhcp::HWAddrPtr& hwaddr) const;
+
     /// \brief Generate DUID.
     ///
     /// Method generates unique DUID. The number of DUIDs it can generate
@@ -527,7 +545,7 @@ protected:
     /// \throw isc::InvalidOperation if broadcast option can't be
     /// set for the v4 socket or if multicast option can't be set
     /// for the v6 socket.
-    /// \throw isc::Unexpected if interal unexpected error occured.
+    /// \throw isc::Unexpected if internal unexpected error occurred.
     /// \return socket descriptor.
     int openSocket() const;
 
@@ -562,7 +580,7 @@ protected:
     /// \param [in] socket socket to be used.
     /// \param [in] pkt4 object representing DHCPv4 packet received.
     /// \throw isc::BadValue if unknown message type received.
-    /// \throw isc::Unexpected if unexpected error occured.
+    /// \throw isc::Unexpected if unexpected error occurred.
     void processReceivedPacket4(const TestControlSocket& socket,
                                 const dhcp::Pkt4Ptr& pkt4);
 
@@ -579,7 +597,7 @@ protected:
     /// \param [in] socket socket to be used.
     /// \param [in] pkt6 object representing DHCPv6 packet received.
     /// \throw isc::BadValue if unknown message type received.
-    /// \throw isc::Unexpected if unexpected error occured.
+    /// \throw isc::Unexpected if unexpected error occurred.
     void processReceivedPacket6(const TestControlSocket& socket,
                                 const dhcp::Pkt6Ptr& pkt6);
 
@@ -595,7 +613,7 @@ protected:
     ///
     /// \param socket socket to be used.
     /// \throw isc::BadValue if unknown message type received.
-    /// \throw isc::Unexpected if unexpected error occured.
+    /// \throw isc::Unexpected if unexpected error occurred.
     /// \return number of received packets.
     uint64_t receivePackets(const TestControlSocket& socket);
 
@@ -726,6 +744,15 @@ protected:
                      const uint64_t packets_num,
                      const bool preload = false);
 
+    /// \brief Send number of DHCPREQUEST (renew) messages to a server.
+    ///
+    /// \param socket An object representing socket to be used to send packets.
+    /// \param msg_num A number of messages to be sent.
+    ///
+    /// \return A number of messages actually sent.
+    uint64_t sendMultipleRequests(const TestControlSocket& socket,
+                                  const uint64_t msg_num);
+
     /// \brief Send number of DHCPv6 Renew or Release messages to the server.
     ///
     /// \param socket An object representing socket to be used to send packets.
@@ -738,6 +765,14 @@ protected:
                                    const uint32_t msg_type,
                                    const uint64_t msg_num);
 
+    /// \brief Send DHCPv4 renew (DHCPREQUEST) using specified socket.
+    ///
+    /// \param socket An object encapsulating socket to be used to send
+    /// a packet.
+    ///
+    /// \return true if the message has been sent, false otherwise.
+    bool sendRequestFromAck(const TestControlSocket& socket);
+
     /// \brief Send DHCPv6 Renew or Release message using specified socket.
     ///
     /// This method will select an existing lease from the Reply packet cache
@@ -763,7 +798,7 @@ protected:
     /// \param discover_pkt4 DISCOVER packet sent.
     /// \param offer_pkt4 OFFER packet object.
     ///
-    /// \throw isc::Unexpected if unexpected error occured.
+    /// \throw isc::Unexpected if unexpected error occurred.
     /// \throw isc::InvalidOperation if Statistics Manager has not been
     /// initialized.
     /// \throw isc::dhcp::SocketWriteError if failed to send the packet.
@@ -800,7 +835,7 @@ protected:
     ///
     /// \param socket socket to be used to send message.
     /// \param advertise_pkt6 ADVERTISE packet object.
-    /// \throw isc::Unexpected if unexpected error occured.
+    /// \throw isc::Unexpected if unexpected error occurred.
     /// \throw isc::InvalidOperation if Statistics Manager has not been
     /// initialized.
     ///
@@ -971,7 +1006,7 @@ protected:
     /// \brief Return transaction id offset in a packet.
     ///
     /// \param arg_idx command line argument index to be used.
-    /// If multiple -X parameters specifed it points to the
+    /// If multiple -X parameters specified it points to the
     /// one to be used.
     /// \return transaction id offset in packet.
     int getTransactionIdOffset(const int arg_idx) const;
@@ -1077,6 +1112,7 @@ protected:
     StatsMgr4Ptr stats_mgr4_;  ///< Statistics Manager 4.
     StatsMgr6Ptr stats_mgr6_;  ///< Statistics Manager 6.
 
+    PacketStorage<dhcp::Pkt4> ack_storage_; ///< A storage for DHCPACK messages.
     PacketStorage<dhcp::Pkt6> reply_storage_; ///< A storage for reply messages.
 
     NumberGeneratorPtr transid_gen_; ///< Transaction id generator.
@@ -1089,7 +1125,7 @@ protected:
     TemplateBufferCollection template_buffers_;
 
     /// First packets send. They are used at the end of the test
-    /// to print packet templates when diagnostics flag T is specifed.
+    /// to print packet templates when diagnostics flag T is specified.
     std::map<uint8_t, dhcp::Pkt4Ptr> template_packets_v4_;
     std::map<uint8_t, dhcp::Pkt6Ptr> template_packets_v6_;
 

+ 7 - 8
src/bin/perfdhcp/tests/command_options_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -341,6 +341,9 @@ TEST_F(CommandOptionsTest, RenewRate) {
     // that order doesn't matter.
     EXPECT_NO_THROW(process("perfdhcp -6 -f 5 -r 10 -l ethx all"));
     EXPECT_EQ(5, opt.getRenewRate());
+    // Renew rate should also be accepted for DHCPv4 case.
+    EXPECT_NO_THROW(process("perfdhcp -4 -f 5 -r 10 -l ethx all"));
+    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);
@@ -354,10 +357,6 @@ TEST_F(CommandOptionsTest, RenewRate) {
     // be accepted.
     EXPECT_THROW(process("perfdhcp -6 -f 10 -l ethx all"),
                  isc::InvalidParameter);
-    // Currently the -f<renew-rate> can be specified for IPv6 mode
-    // only.
-    EXPECT_THROW(process("perfdhcp -4 -r 10 -f 10 -l ethx all"),
-                 isc::InvalidParameter);
     // Renew rate should be specified.
     EXPECT_THROW(process("perfdhcp -6 -r 10 -f -l ethx all"),
                  isc::InvalidParameter);
@@ -384,7 +383,7 @@ TEST_F(CommandOptionsTest, ReleaseRate) {
     // 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.
+    // The negative release-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
@@ -634,7 +633,7 @@ TEST_F(CommandOptionsTest, Seed) {
     EXPECT_EQ(0, opt.getSeed());
     EXPECT_FALSE(opt.isSeeded());
 
-    // Negtaive test cases
+    // Negative test cases
     // Seed must be non-negative integer
     EXPECT_THROW(process("perfdhcp -6 -P 2 -s -5 -l ethx all"),
                  isc::InvalidParameter);
@@ -768,7 +767,7 @@ TEST_F(CommandOptionsTest, Interface) {
     // at least one interface name on OS where test is run.
     // Interface Manager has ability to detect interfaces.
     // Although we don't call initIsInterface explicitly
-    // here it is called by CommandOptions object interally
+    // here it is called by CommandOptions object internally
     // so this function is covered by the test.
     dhcp::IfaceMgr& iface_mgr = dhcp::IfaceMgr::instance();
     const dhcp::IfaceMgr::IfaceCollection& ifaces = iface_mgr.getIfaces();

+ 1 - 1
src/bin/perfdhcp/tests/perf_pkt4_unittest.cc

@@ -68,7 +68,7 @@ public:
             255, 255, 255, 255,     // giaddr
         };
 
-	// cppcheck-suppress variableScope
+        // cppcheck-suppress variableScope
         uint8_t v4Opts[] = {
             DHO_HOST_NAME, 3, 0,   1,  2,  // Host name option.
             DHO_BOOT_SIZE, 3, 10, 11, 12,  // Boot file size option

+ 8 - 5
src/bin/perfdhcp/tests/stats_mgr_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -50,7 +50,7 @@ public:
         // Packet timestamp is normally updated by interface
         // manager on packets reception or send. Unit tests
         // do not use interface manager so we need to do it
-        // ourselfs.
+        // ourselves.
         pkt->updateTimestamp();
         return pkt;
     }
@@ -68,7 +68,7 @@ public:
         // Packet timestamp is normally updated by interface
         // manager on packets reception or send. Unit tests
         // do not use interface manager so we need to do it
-        // ourselfs.
+        // ourselves.
         pkt->updateTimestamp();
         return pkt;
     }
@@ -238,7 +238,7 @@ TEST_F(StatsMgrTest, MultipleExchanges) {
     passMultiplePackets6(stats_mgr, StatsMgr6::XCHG_RR, DHCPV6_REQUEST,
                          request_packets_num);
 
-    // Check if all packets are successfuly passed to packet lists.
+    // Check if all packets are successfully passed to packet lists.
     EXPECT_EQ(solicit_packets_num,
               stats_mgr->getSentPacketsNum(StatsMgr6::XCHG_SA));
     EXPECT_EQ(request_packets_num,
@@ -265,6 +265,9 @@ TEST_F(StatsMgrTest, ExchangeToString) {
     EXPECT_EQ("DISCOVER-OFFER",
               StatsMgr4::exchangeToString(StatsMgr4::XCHG_DO));
     EXPECT_EQ("REQUEST-ACK", StatsMgr4::exchangeToString(StatsMgr4::XCHG_RA));
+    EXPECT_EQ("REQUEST-ACK (renewal)",
+              StatsMgr4::exchangeToString(StatsMgr4::XCHG_RNA));
+
 
     // Test DHCPv6 specific exchange names.
     EXPECT_EQ("SOLICIT-ADVERTISE",
@@ -375,7 +378,7 @@ TEST_F(StatsMgrTest, Delays) {
               std::numeric_limits<double>::max());
     EXPECT_GT(stats_mgr->getMinDelay(StatsMgr4::XCHG_DO), 1);
 
-    // Max delay is supposed to the same value as mininimum
+    // Max delay is supposed to the same value as minimum
     // or maximum delay.
     EXPECT_GT(stats_mgr->getMaxDelay(StatsMgr4::XCHG_DO), 1);
 

+ 233 - 29
src/bin/perfdhcp/tests/test_control_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -69,7 +69,7 @@ public:
         uint32_t transid_; ///< Last generated transaction id.
     };
 
-    /// \brief Sets the due times for sedning Solicit, Renew and Release.
+    /// \brief Sets the due times for sending Solicit, Renew and Release.
     ///
     /// There are three class members that hold the due time for sending DHCP
     /// messages:
@@ -95,12 +95,14 @@ public:
 
     using TestControl::checkExitConditions;
     using TestControl::createMessageFromReply;
+    using TestControl::createRequestFromAck;
     using TestControl::factoryElapsedTime6;
     using TestControl::factoryGeneric;
     using TestControl::factoryIana6;
     using TestControl::factoryOptionRequestOption6;
     using TestControl::factoryRapidCommit6;
     using TestControl::factoryRequestList4;
+    using TestControl::generateClientId;
     using TestControl::generateDuid;
     using TestControl::generateMacAddress;
     using TestControl::getCurrentTimeout;
@@ -114,6 +116,7 @@ public:
     using TestControl::reset;
     using TestControl::sendDiscover4;
     using TestControl::sendPackets;
+    using TestControl::sendMultipleRequests;
     using TestControl::sendMultipleMessages6;
     using TestControl::sendRequest6;
     using TestControl::sendSolicit6;
@@ -265,7 +268,7 @@ public:
     /// \param requested_options reference buffer with options.
     /// \param buf test buffer with options that will be matched.
     /// \return number of options from the buffer matched with options in
-    /// the reference buffer or -1 if error occured.
+    /// the reference buffer or -1 if error occurred.
     int matchRequestedOptions6(const dhcp::OptionBuffer& requested_options,
                                const dhcp::OptionBuffer& buf) const {
         // Sanity check.
@@ -316,7 +319,7 @@ public:
         return (cnt);
     }
 
-    /// \brief Test generation of mulitple DUIDs
+    /// \brief Test generation of multiple DUIDs
     ///
     /// This method checks the generation of multiple DUIDs. Number
     /// of iterations depends on the number of simulated clients.
@@ -403,7 +406,7 @@ public:
             // if randomization algorithm generates the same values but
             // this would be an error in randomization algorithm.
             total_dist += mismatch_dist;
-            // Mismatch may have occured on the DUID octet position
+            // Mismatch may have occurred on the DUID octet position
             // up to calculated earlier unequal_pos.
             ASSERT_LE(mismatch_dist, unequal_pos);
             // unique will inform if tested DUID is unique.
@@ -433,7 +436,7 @@ public:
             // Remember generated DUID.
             duids.push_back(new_duid);
         }
-        // If we have more than one client at least one mismatch occured.
+        // If we have more than one client at least one mismatch occurred.
         if (clients_num < 2) {
             EXPECT_EQ(0, total_dist);
         }
@@ -580,7 +583,7 @@ public:
         int clients_num = CommandOptions::instance().getClientsNum();
         // The old_mac will be holding the value of previously generated
         // MAC address. We will be comparing the newly generated one with it
-        // to see if it changes when mulitple clients are simulated or if it
+        // to see if it changes when multiple clients are simulated or if it
         // does not change when single client is simulated.
         MacAddress old_mac(CommandOptions::instance().getMacTemplate());
         // Holds the position if the octet on which two MAC addresses can
@@ -614,7 +617,7 @@ public:
             // the case if randomization algorithm generates the same
             // values but this would be an error in randomization algorithm.
             total_dist += mismatch_dist;
-            // Mismatch may have occured on the MAC address'es octet position
+            // Mismatch may have occurred on the MAC address'es octet position
             // up to calculated earlier unequal_pos.
             ASSERT_LE(mismatch_dist, unequal_pos);
             // unique will inform if tested DUID is unique.
@@ -650,7 +653,143 @@ public:
         }
     }
 
-    /// \brief Test that the DHCPv4 Release or Renew message is created
+    /// \brief Test sending DHCPv4 renews.
+    ///
+    /// This function simulates acquiring 10 leases from the server. Returned
+    /// DHCPACK messages are cached and used to send renew messages.
+    /// The maximal number of 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 messages than the number of leases acquired
+    /// will fail.
+    void testSendRenew4() {
+        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 -4 -l " << loopback_iface << " -r 10 -f";
+        s << " 10 -R 10 -L 10067 -n 10 127.0.0.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 DHCPDISCOVER messages. Each generated message will
+        // be assigned a different transaction id, starting from 1 to 10.
+        tc.sendPackets(sock, 10);
+
+        // Simulate DHCPOFFER responses from the server. Each DHCPOFFER is
+        // assigned a transaction id from the range of 1 to 10, so as they
+        // match the transaction ids from the DHCPDISCOVER messages.
+        for (unsigned i = generator->getNext() - 10;
+             i < generator->getNext(); ++i) {
+            Pkt4Ptr offer(createOfferPkt4(i));
+            // If DHCPOFFER is matched with the DHCPDISCOVER the call below
+            // will trigger a corresponding DHCPREQUEST. They will be assigned
+            // transaction ids from the range from 11 to 20 (the range of
+            // 1 to 10 has been used by DHCPDISCOVER-DHCPOFFER).
+            ASSERT_NO_THROW(tc.processReceivedPacket4(sock, offer));
+    }
+
+        // Requests have been sent, so now let's simulate responses from the
+        // server. Generate corresponding DHCPACK messages with the transaction
+        // ids from the range from 11 to 20.
+        for (unsigned i = generator->getNext() - 10;
+             i < generator->getNext(); ++i) {
+            Pkt4Ptr ack(createAckPkt4(i));
+            // Each DHCPACK 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.processReceivedPacket4(sock, ack));
+        }
+
+        uint64_t msg_num;
+        // Try to send 5 messages. It should be successful because 10
+        // DHCPREQUEST messages has been received. For each of them we
+        // should be able to send renewal.
+        ASSERT_NO_THROW(
+            msg_num = tc.sendMultipleRequests(sock, 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 haven't been sent yet.
+        ASSERT_NO_THROW(
+            msg_num = tc.sendMultipleRequests(sock, 5)
+        );
+        EXPECT_EQ(5, msg_num);
+
+        // We used all the DHCPACK packets (we sent renew or release for each of
+        // them already). Therefore, no further renew messages should be sent
+        // before we acquire new leases.
+        ASSERT_NO_THROW(
+            msg_num = tc.sendMultipleRequests(sock, 5)
+        );
+        // Make sure that no message has been sent.
+        EXPECT_EQ(0, msg_num);
+    }
+
+    /// \brief Test that the DHCPREQUEST message is created correctly and
+    /// comprises expected values.
+    void testCreateRequest() {
+        // 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 -4 -l lo -r 10 -f 10";
+        s << " -R 10 -L 10067 -n 10 127.0.0.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);
+
+        Pkt4Ptr ack = createAckPkt4(1);
+
+        // Create DHCPREQUST from DHCPACK.
+        Pkt4Ptr request;
+        ASSERT_NO_THROW(request = tc.createRequestFromAck(ack));
+
+        // Make sure that the DHCPACK has been successfully created and that
+        // it holds expected data.
+        ASSERT_TRUE(request);
+        EXPECT_EQ("127.0.0.1", request->getCiaddr().toText());
+
+        // HW address.
+        HWAddrPtr hwaddr_ack = ack->getHWAddr();
+        ASSERT_TRUE(hwaddr_ack);
+        HWAddrPtr hwaddr_req = request->getHWAddr();
+        ASSERT_TRUE(hwaddr_req);
+        EXPECT_TRUE(hwaddr_ack->hwaddr_ == hwaddr_req->hwaddr_);
+
+        // Creating message from null DHCPACK should fail.
+        EXPECT_THROW(tc.createRequestFromAck(Pkt4Ptr()), isc::BadValue);
+
+        // Creating message from DHCPACK holding zero yiaddr should fail.
+        asiolink::IOAddress yiaddr = ack->getYiaddr();
+        ack->setYiaddr(asiolink::IOAddress::IPV4_ZERO_ADDRESS());
+        EXPECT_THROW(tc.createRequestFromAck(ack), isc::BadValue);
+        ack->setYiaddr(yiaddr);
+    }
+
+    /// \brief Test that the DHCPv6 Release or Renew message is created
     /// correctly and comprises expected options.
     ///
     /// \param msg_type A type of the message to be tested: DHCPV6_RELEASE
@@ -721,7 +860,7 @@ public:
     ///
     /// 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
+    /// The maximal 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.
@@ -816,26 +955,47 @@ public:
     /// \brief Parse command line string with CommandOptions.
     ///
     /// \param cmdline command line string to be parsed.
-    /// \throw isc::Unexpected if unexpected error occured.
+    /// \throw isc::Unexpected if unexpected error occurred.
     /// \throw isc::InvalidParameter if command line is invalid.
     void processCmdLine(const std::string& cmdline) const {
         CommandOptionsHelper::process(cmdline);
     }
 
+    /// \brief Create DHCPOFFER or DHCPACK packet.
+    ///
+    /// \param pkt_type DHCPOFFER or DHCPACK.
+    /// \param transid Transaction id.
+    ///
+    /// \return Instance of the packet.
+    Pkt4Ptr
+    createResponsePkt4(const uint8_t pkt_type,
+                       const uint32_t transid) const {
+        Pkt4Ptr pkt(new Pkt4(pkt_type, transid));
+        OptionPtr opt_serverid = Option::factory(Option::V4,
+                                                 DHO_DHCP_SERVER_IDENTIFIER,
+                                                 OptionBuffer(4, 1));
+        pkt->setYiaddr(asiolink::IOAddress("127.0.0.1"));
+        pkt->addOption(opt_serverid);
+        pkt->updateTimestamp();
+        return (pkt);
+    }
+
     /// \brief Create DHCPv4 OFFER packet.
     ///
     /// \param transid transaction id.
     /// \return instance of the packet.
-    boost::shared_ptr<Pkt4>
+    Pkt4Ptr
     createOfferPkt4(uint32_t transid) const {
-        boost::shared_ptr<Pkt4> offer(new Pkt4(DHCPOFFER, transid));
-        OptionPtr opt_serverid = Option::factory(Option::V4,
-                                                 DHO_DHCP_SERVER_IDENTIFIER,
-                                                 OptionBuffer(4, 1));
-        offer->setYiaddr(asiolink::IOAddress("127.0.0.1"));
-        offer->addOption(opt_serverid);
-        offer->updateTimestamp();
-        return (offer);
+        return (createResponsePkt4(DHCPOFFER, transid));
+    }
+
+    /// \brief Create DHCPACK packet.
+    ///
+    /// \param transid transaction id.
+    /// \return instance of the packet.
+    Pkt4Ptr
+    createAckPkt4(const uint32_t transid) const {
+        return (createResponsePkt4(DHCPACK, transid));
     }
 
     /// \brief Create DHCPv6 ADVERTISE packet.
@@ -916,6 +1076,34 @@ TEST_F(TestControlTest, reset) {
 
 }
 
+// This test verifies that the client id is generated from the HW address.
+TEST_F(TestControlTest, generateClientId) {
+    // Generate HW address.
+    std::vector<uint8_t> hwaddr;
+    for (unsigned int i = 0; i < 6; ++i) {
+        hwaddr.push_back(i);
+    }
+    HWAddrPtr hwaddr_ptr(new HWAddr(hwaddr, 5));
+
+    // Use generated HW address to generate client id.
+    NakedTestControl tc;
+    OptionPtr opt_client_id;
+    ASSERT_NO_THROW(opt_client_id = tc.generateClientId(hwaddr_ptr));
+    ASSERT_TRUE(opt_client_id);
+
+    // Extract the client id data.
+    const OptionBuffer& client_id = opt_client_id->getData();
+    ASSERT_EQ(7, client_id.size());
+
+    // Verify that the client identifier is generated correctly.
+
+    // First byte is the HW type.
+    EXPECT_EQ(5, client_id[0]);
+    // The rest of the client identifier should be equal to the HW address.
+    std::vector<uint8_t> sub(client_id.begin() + 1, client_id.end());
+    EXPECT_TRUE(hwaddr == sub);
+}
+
 TEST_F(TestControlTest, GenerateDuid) {
     // Simple command line that simulates one client only. Always the
     // same DUID will be generated.
@@ -1252,7 +1440,7 @@ TEST_F(TestControlTest, Packet6ExchangeFromTemplate) {
     // then test should be interrupted and actual number of iterations will
     // be 6.
     const int received_num = 3;
-    // Simulate the number of Solicit-Advertise-Request-Reply (SARR) echanges.
+    // Simulate the number of Solicit-Advertise-Request-Reply (SARR) exchanges.
     // The test function generates server's responses and passes it to the
     // TestControl class methods for processing. The number of exchanges
     // actually performed is returned in 'iterations_performed' argument. If
@@ -1287,14 +1475,14 @@ TEST_F(TestControlTest, Packet6Exchange) {
     // This simulates no packet drops.
     bool use_templates = false;
 
-    // Simulate the number of Solicit-Advertise-Request-Reply (SARR) echanges.
+    // Simulate the number of Solicit-Advertise-Request-Reply (SARR) exchanges.
     // The test function generates server's responses and passes it to the
     // TestControl class methods for processing. The number of exchanges
     // actually performed is returned in 'iterations_performed' argument. If
     // processing is successful, the number of performed iterations should be
     // equal to the number of exchanges specified with the '-n' command line
     // parameter (10 in this case). All exchanged packets carry the IA_NA option
-    // to simulate the IPv6 address acqusition and to verify that the IA_NA
+    // to simulate the IPv6 address acquisition and to verify that the IA_NA
     // options returned by the server are processed correctly.
     testPkt6Exchange(iterations_num, iterations_num, use_templates,
                      iterations_performed);
@@ -1323,7 +1511,7 @@ TEST_F(TestControlTest, Packet6ExchangePrefixDelegation) {
     // This simulates no packet drops.
     bool use_templates = false;
 
-    // Simulate the number of Solicit-Advertise-Request-Reply (SARR) echanges.
+    // Simulate the number of Solicit-Advertise-Request-Reply (SARR) exchanges.
     // The test function generates server's responses and passes it to the
     // TestControl class methods for processing. The number of exchanges
     // actually performed is returned in 'iterations_performed' argument. If
@@ -1358,7 +1546,7 @@ TEST_F(TestControlTest, Packet6ExchangeAddressAndPrefix) {
     // Set number of received packets equal to number of iterations.
     // This simulates no packet drops.
     bool use_templates = false;
-    // Simulate the number of Solicit-Advertise-Request-Reply (SARR) echanges.
+    // Simulate the number of Solicit-Advertise-Request-Reply (SARR) exchanges.
     // The test function generates server's responses and passes it to the
     // TestControl class methods for processing. The number of exchanges
     // actually performed is returned in 'iterations_performed' argument. If
@@ -1432,14 +1620,30 @@ TEST_F(TestControlTest, PacketTemplates) {
     EXPECT_THROW(tc.initPacketTemplates(), isc::BadValue);
 }
 
-TEST_F(TestControlTest, processRenew) {
+// This test verifies that DHCPv4 renew (DHCPREQUEST) messages can be
+// sent for acquired leases.
+TEST_F(TestControlTest, processRenew4) {
+    testSendRenew4();
+}
+
+// This test verifies that DHCPv6 Renew messages can be sent for acquired
+// leases.
+TEST_F(TestControlTest, processRenew6) {
     testSendRenewRelease(DHCPV6_RENEW);
 }
 
-TEST_F(TestControlTest, processRelease) {
+// This test verifies that DHCPv6 Release messages can be sent for acquired
+// leases.
+TEST_F(TestControlTest, processRelease6) {
     testSendRenewRelease(DHCPV6_RELEASE);
 }
 
+// This test verifies that DHCPREQUEST is created correctly from the
+// DHCPACK message.
+TEST_F(TestControlTest, createRequest) {
+    testCreateRequest();
+}
+
 // This test verifies that the DHCPV6 Renew message is created correctly
 // and that it comprises all required options.
 TEST_F(TestControlTest, createRenew) {
@@ -1485,8 +1689,8 @@ TEST_F(TestControlTest, getCurrentTimeout) {
 // 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.
+// due time to send Renew - the timeout should be adjusted 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"));