Browse Source

[1959] Send request messages to server and corresponding unit tests.

Marcin Siodelski 12 years ago
parent
commit
fbbae39aea

+ 50 - 25
tests/tools/perfdhcp/stats_mgr.h

@@ -47,8 +47,8 @@ namespace perfdhcp {
 /// stored on the list of sent packets. When packets are matched the
 /// round trip time can be calculated.
 ///
-/// \tparam T class representing DHCPv4 or DHCPv6 packet.
-template <class T>
+/// \param T class representing DHCPv4 or DHCPv6 packet.
+template <class T = dhcp::Pkt4>
 class StatsMgr : public boost::noncopyable {
 public:
 
@@ -138,7 +138,7 @@ public:
         /// \param packet packet which transaction id is to be hashed.
         /// \throw isc::BadValue if packet is null.
         /// \return transaction id hash.
-        static uint32_t hashTransid(const boost::shared_ptr<const T>& packet) {
+        static uint32_t hashTransid(const boost::shared_ptr<T>& packet) {
             if (!packet) {
                 isc_throw(BadValue, "Packet is null");
             }
@@ -214,12 +214,12 @@ public:
         /// }
         /// \endcode
         typedef boost::multi_index_container<
-            boost::shared_ptr<const T>,
+            boost::shared_ptr<T>,
             boost::multi_index::indexed_by<
                 boost::multi_index::sequenced<>,
                 boost::multi_index::hashed_non_unique<
                         boost::multi_index::global_fun<
-                            const boost::shared_ptr<const T>&,
+                            const boost::shared_ptr<T>&,
                             uint32_t,
                             &ExchangeStats::hashTransid
                         >
@@ -228,7 +228,7 @@ public:
         > PktList;
 
         /// Packet list iterator for sequencial access to elements.
-        typedef typename PktList::const_iterator PktListIterator;
+        typedef typename PktList::iterator PktListIterator;
         /// Packet list index to search packets using transaction id hash.
         typedef typename PktList::template nth_index<1>::type
             PktListTransidHashIndex;
@@ -267,7 +267,7 @@ public:
         ///
         /// \param packet packet object to be added.
         /// \throw isc::BadValue if packet is null.
-        void appendSent(const boost::shared_ptr<const T>& packet) {
+        void appendSent(const boost::shared_ptr<T>& packet) {
             if (!packet) {
                 isc_throw(BadValue, "Packet is null");
             }
@@ -281,7 +281,7 @@ public:
         ///
         /// \param packet packet object to be added.
         /// \throw isc::BadValue if packet is null.
-        void appendRcvd(const boost::shared_ptr<const T>& packet) {
+        void appendRcvd(const boost::shared_ptr<T>& packet) {
             if (!packet) {
                 isc_throw(BadValue, "Packet is null");
             }
@@ -297,8 +297,8 @@ public:
         /// \param rcvd_packet received packet
         /// \throw isc::BadValue if sent or received packet is null.
         /// \throw isc::Unexpected if failed to calculate timestamps
-        void updateDelays(const boost::shared_ptr<const T>& sent_packet,
-                          const boost::shared_ptr<const T>& rcvd_packet) {
+        void updateDelays(const boost::shared_ptr<T>& sent_packet,
+                          const boost::shared_ptr<T>& rcvd_packet) {
             if (!sent_packet) {
                 isc_throw(BadValue, "Sent packet is null");
             }
@@ -356,7 +356,8 @@ public:
         /// \throw isc::BadValue if received packet is null.
         /// \return packet having specified transaction or NULL if packet
         /// not found
-        boost::shared_ptr<const T> matchPackets(const boost::shared_ptr<const T>& rcvd_packet) {
+        boost::shared_ptr<T>
+        matchPackets(const boost::shared_ptr<T>& rcvd_packet) {
             if (!rcvd_packet) {
                 isc_throw(BadValue, "Received packet is null");
             }
@@ -367,7 +368,7 @@ public:
                 // that the received packet we got has no corresponding
                 // sent packet so orphans counter has to be updated.
                 ++orphans_;
-                return(boost::shared_ptr<const T>());
+                return(boost::shared_ptr<T>());
             } else if (next_sent_ == sent_packets_.end()) {
                 // Even if there are still many unmatched packets on the
                 // list we might hit the end of it because of unordered
@@ -426,13 +427,13 @@ public:
                 // If we are here, it means that both ordered lookup and
                 // unordered lookup failed. Searched packet is not on the list.
                 ++orphans_;
-                return(boost::shared_ptr<const T>());
+                return(boost::shared_ptr<T>());
             }
 
             // Packet is matched so we count it. We don't count unmatched packets
             // as they are counted as orphans with a separate counter.
             ++rcvd_packets_num_;
-            boost::shared_ptr<const T> sent_packet(*next_sent_);
+            boost::shared_ptr<T> sent_packet(*next_sent_);
             // If packet was found, we assume it will be never searched
             // again. We want to delete this packet from the list to
             // improve performance of future searches.
@@ -549,6 +550,19 @@ public:
         /// \return number of received packets.
         uint64_t getRcvdPacketsNum() const { return(rcvd_packets_num_); }
 
+        /// \brief Return number of dropped packets.
+        ///
+        /// Method returns number of dropped packets.
+        ///
+        /// \return number of dropped packets.
+        uint64_t getDroppedPacketsNum() const {
+            uint64_t drops = 0;
+            if (getSentPacketsNum() > getRcvdPacketsNum()) {
+                drops = getSentPacketsNum() - getRcvdPacketsNum();
+            }
+            return(drops);
+        }
+
         /// \brief Print main statistics for packet exchange.
         ///
         /// Method prints main statistics for particular exchange.
@@ -556,13 +570,9 @@ public:
         /// number of dropped packets and number of orphans.
         void printMainStats() const {
             using namespace std;
-            uint64_t drops = 0;
-            if (getRcvdPacketsNum() >= getSentPacketsNum()) {
-                drops = getRcvdPacketsNum() - getSentPacketsNum();
-            }
             cout << "sent packets: " << getSentPacketsNum() << endl
                  << "received packets: " << getRcvdPacketsNum() << endl
-                 << "drops: " << drops << endl
+                 << "drops: " << getDroppedPacketsNum() << endl
                  << "orphans: " << getOrphans() << endl;
         }
 
@@ -614,7 +624,7 @@ public:
             for (PktListIterator it = rcvd_packets_.begin();
                  it != rcvd_packets_.end();
                  ++it) {
-                boost::shared_ptr<const T> rcvd_packet = *it;
+                boost::shared_ptr<T> rcvd_packet = *it;
                 PktListTransidHashIndex& idx =
                     archived_packets_.template get<1>();
                 std::pair<PktListTransidHashIterator,
@@ -625,7 +635,7 @@ public:
                      ++it) {
                     if ((*it_archived)->getTransid() ==
                         rcvd_packet->getTransid()) {
-                        boost::shared_ptr<const T> sent_packet = *it_archived;
+                        boost::shared_ptr<T> sent_packet = *it_archived;
                         // Get sent and received packet times.
                         ptime sent_time = sent_packet->getTimestamp();
                         ptime rcvd_time = rcvd_packet->getTimestamp();
@@ -839,7 +849,7 @@ public:
     /// \throw isc::BadValue if invalid exchange type specified or
     /// packet is null.
     void passSentPacket(const ExchangeType xchg_type,
-                        const boost::shared_ptr<const T>& packet) {
+                        const boost::shared_ptr<T>& packet) {
         ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
         xchg_stats->appendSent(packet);
     }
@@ -857,10 +867,11 @@ public:
     /// or packet is null.
     /// \throw isc::Unexpected if corresponding packet was not
     /// found on the list of sent packets.
-    void passRcvdPacket(const ExchangeType xchg_type,
-                        const boost::shared_ptr<const T>& packet) {
+    boost::shared_ptr<T>
+    passRcvdPacket(const ExchangeType xchg_type,
+                   const boost::shared_ptr<T>& packet) {
         ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
-        boost::shared_ptr<const T> sent_packet
+        boost::shared_ptr<T> sent_packet
             = xchg_stats->matchPackets(packet);
 
         if (sent_packet) {
@@ -869,6 +880,7 @@ public:
                 xchg_stats->appendRcvd(packet);
             }
         }
+        return(sent_packet);
     }
 
     /// \brief Return minumum delay between sent and received packet.
@@ -1003,6 +1015,19 @@ public:
         return(xchg_stats->getRcvdPacketsNum());
     }
 
+    /// \brief Return total number of dropped packets.
+    ///
+    /// Method returns total number of dropped packets for specified
+    /// exchange type.
+    ///
+    /// \param xchg_type exchange type.
+    /// \throw isc::BadValue if invalid exchange type specified.
+    /// \return number of dropped packets.
+    uint64_t getDroppedPacketsNum(const ExchangeType xchg_type) const {
+        ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
+        return(xchg_stats->getDroppedPacketsNum());
+    }
+
     /// \brief Return name of the exchange.
     ///
     /// Method returns name of the specified exchange type.

+ 194 - 34
tests/tools/perfdhcp/test_control.cc

@@ -80,18 +80,104 @@ TestControl::instance() {
 
 TestControl::TestControl() :
     send_due_(microsec_clock::universal_time()),
-    last_sent_(send_due_) {
+    last_sent_(send_due_),
+    transid_gen_(new TransidGenerator()) {
 }
 
 bool
 TestControl::checkExitConditions() const {
     CommandOptions& options = CommandOptions::instance();
-    if ((options.getNumRequests().size() > 0) &&
-        (sent_packets_0_ >= options.getNumRequests()[0])) {
-        return(true);
-    } else if ((options.getNumRequests().size() == 2) &&
-               (sent_packets_1_ >= options.getNumRequests()[1])) {
-        return(true);
+    // Check if we reached maximum number of DISCOVER/SOLICIT sent.
+    if (options.getNumRequests().size() > 0) {
+        if (options.getIpVersion() == 4) {
+            if (getSentPacketsNum(StatsMgr4::XCHG_DO) >=
+                options.getNumRequests()[0]) {
+                return(true);
+            }
+        } else if (options.getIpVersion() == 6) {
+            if (stats_mgr6_->getSentPacketsNum(StatsMgr6::XCHG_SA) >=
+                options.getNumRequests()[0]) {
+                return(true);
+            }
+        }
+    }
+    // Check if we reached maximum number REQUEST packets.
+    if (options.getNumRequests().size() == 2) {
+        if (options.getIpVersion() == 4) {
+            if (stats_mgr4_->getSentPacketsNum(StatsMgr4::XCHG_RA) >=
+                options.getNumRequests()[1]) {
+                return(true);
+            }
+        } else if (options.getIpVersion() == 6) {
+            if (stats_mgr6_->getSentPacketsNum(StatsMgr6::XCHG_RR) >=
+                options.getNumRequests()[1]) {
+                return(true);
+            }
+        }
+    }
+    // Check if we reached maximum number of drops of OFFER/ADVERTISE packets.
+    if (options.getMaxDrop().size() > 0) {
+        if (options.getIpVersion() == 4) {
+            if (stats_mgr4_->getDroppedPacketsNum(StatsMgr4::XCHG_DO) >=
+                options.getMaxDrop()[0]) {
+                return(true);
+            }
+        } else if (options.getIpVersion() == 6) {
+            if (stats_mgr6_->getDroppedPacketsNum(StatsMgr6::XCHG_SA) >=
+                options.getMaxDrop()[0]) {
+                return(true);
+            }
+        }
+    }
+    // Check if we reached maximum number of drops of ACK/REPLY packets.
+    if (options.getMaxDrop().size() == 2) {
+        if (options.getIpVersion() == 4) {
+            if (stats_mgr4_->getDroppedPacketsNum(StatsMgr4::XCHG_RA) >=
+                options.getMaxDrop()[1]) {
+                return(true);
+            }
+        } else if (options.getIpVersion() == 6) {
+            if (stats_mgr6_->getDroppedPacketsNum(StatsMgr6::XCHG_RR) >=
+                options.getMaxDrop()[1]) {
+                return(true);
+            }
+        }
+    }
+    // Check if we reached maximum drops percentage of OFFER/ADVERTISE packets.
+    if (options.getMaxDropPercentage().size() > 0) {
+        if (options.getIpVersion() == 4) {
+            if ((stats_mgr4_->getSentPacketsNum(StatsMgr4::XCHG_DO) > 10) &&
+                ((100. * stats_mgr4_->getDroppedPacketsNum(StatsMgr4::XCHG_DO) /
+                 stats_mgr4_->getSentPacketsNum(StatsMgr4::XCHG_DO)) >=
+                 options.getMaxDropPercentage()[0])) {
+                return(true);
+            }
+        } else if (options.getIpVersion() == 6) {
+            if ((stats_mgr6_->getSentPacketsNum(StatsMgr6::XCHG_SA) > 10) &&
+                ((100. * stats_mgr6_->getDroppedPacketsNum(StatsMgr6::XCHG_SA) /
+                  stats_mgr6_->getSentPacketsNum(StatsMgr6::XCHG_SA)) >=
+                 options.getMaxDropPercentage()[0])) {
+                return(true);
+            }
+        }
+    }
+    // Check if we reached maximum drops percentage of ACK/REPLY packets.
+    if (options.getMaxDropPercentage().size() == 2) {
+        if (options.getIpVersion() == 4) {
+            if ((stats_mgr4_->getSentPacketsNum(StatsMgr4::XCHG_RA) > 10) &&
+                ((100. * stats_mgr4_->getDroppedPacketsNum(StatsMgr4::XCHG_RA) /
+                 stats_mgr4_->getSentPacketsNum(StatsMgr4::XCHG_RA)) >=
+                 options.getMaxDropPercentage()[0])) {
+                return(true);
+            }
+        } else if (options.getIpVersion() == 6) {
+            if ((stats_mgr6_->getSentPacketsNum(StatsMgr6::XCHG_RR) > 10) &&
+                ((100. * stats_mgr6_->getDroppedPacketsNum(StatsMgr6::XCHG_RR) /
+                  stats_mgr6_->getSentPacketsNum(StatsMgr6::XCHG_RR)) >=
+                 options.getMaxDropPercentage()[0])) {
+                return(true);
+            }
+        }
     }
     return(false);
 }
@@ -170,8 +256,6 @@ TestControl::factoryRequestList4(Option::Universe u,
     return opt;
 }
 
-
-
 std::vector<uint8_t>
 TestControl::generateMacAddress() const {
     CommandOptions& options = CommandOptions::instance();
@@ -268,6 +352,26 @@ TestControl::getNextExchangesNum() const {
     return (0);
 }
 
+uint64_t
+TestControl::getRcvdPacketsNum(const ExchangeType xchg_type) const {
+    uint8_t ip_version = CommandOptions::instance().getIpVersion();
+    if (ip_version == 4) {
+        return(stats_mgr4_->getRcvdPacketsNum(xchg_type));
+    }
+    return(stats_mgr6_->
+           getRcvdPacketsNum(static_cast<StatsMgr6::ExchangeType>(xchg_type)));
+}
+
+uint64_t
+TestControl::getSentPacketsNum(const ExchangeType xchg_type) const {
+    uint8_t ip_version = CommandOptions::instance().getIpVersion();
+    if (ip_version == 4) {
+        return(stats_mgr4_->getSentPacketsNum(xchg_type));
+    }
+    return(stats_mgr6_->
+           getSentPacketsNum(static_cast<StatsMgr6::ExchangeType>(xchg_type)));
+}
+
 void
 TestControl::initializeStatsMgr() {
     CommandOptions& options = CommandOptions::instance();
@@ -275,7 +379,7 @@ TestControl::initializeStatsMgr() {
         stats_mgr4_.reset();
         stats_mgr4_ = StatsMgr4Ptr(new StatsMgr4());
         stats_mgr4_->addExchangeStats(StatsMgr4::XCHG_DO);
-        if (options.getExchangeMode() == CommandOptions::DO_SA) {
+        if (options.getExchangeMode() == CommandOptions::DORA_SARR) {
             stats_mgr4_->addExchangeStats(StatsMgr4::XCHG_RA);
         }
 
@@ -283,9 +387,9 @@ TestControl::initializeStatsMgr() {
         stats_mgr6_.reset();
         stats_mgr6_ = StatsMgr6Ptr(new StatsMgr6());
         stats_mgr6_->addExchangeStats(StatsMgr6::XCHG_SA);
-        if (options.getExchangeMode() == CommandOptions::DO_SA) {
+        if (options.getExchangeMode() == CommandOptions::DORA_SARR) {
             stats_mgr6_->addExchangeStats(StatsMgr6::XCHG_RR);
-        } 
+        }
     }
 }
 
@@ -395,7 +499,8 @@ TestControl::printStats() const {
 }
 
 void
-TestControl::receivePacket4(Pkt4Ptr& pkt4) {
+TestControl::receivePacket4(const TestControlSocket&,
+                            const Pkt4Ptr& pkt4) {
     switch(pkt4->getType()) {
     case DHCPOFFER :
         stats_mgr4_->passRcvdPacket(StatsMgr4::XCHG_DO, pkt4);
@@ -410,23 +515,25 @@ TestControl::receivePacket4(Pkt4Ptr& pkt4) {
 }
 
 void
-TestControl::receivePacket6(Pkt6Ptr& pkt6) {
-    switch(pkt6->getType()) {
-    case DHCPV6_ADVERTISE :
-        stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_SA, pkt6);
-        break;
-    case DHCPV6_REPLY :
+TestControl::receivePacket6(const TestControlSocket& socket,
+                            const Pkt6Ptr& pkt6) {
+    uint8_t packet_type = pkt6->getType();
+    if (packet_type == DHCPV6_ADVERTISE) {
+        Pkt6Ptr solicit_pkt6(stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_SA,
+                                                         pkt6));
+        if (solicit_pkt6) {
+            sendRequest6(socket, solicit_pkt6, pkt6);
+        }
+    } else if (packet_type == DHCPV6_REPLY) {
         stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RR, pkt6);
-        break;
-    default:
+    } else {
         isc_throw(BadValue, "unknown type " << pkt6->getType()
                   << " of received DHCPv6 packet");
     }
-
 }
 
 void
-TestControl::receivePackets() {
+TestControl::receivePackets(const TestControlSocket& socket) {
     int timeout = 0;
     bool receiving = true;
     while (receiving) {
@@ -436,7 +543,7 @@ TestControl::receivePackets() {
                 receiving = false;
             } else {
                 pkt4->unpack();
-                receivePacket4(pkt4);
+                receivePacket4(socket, pkt4);
             }
         } else if (CommandOptions::instance().getIpVersion() == 6) {
             Pkt6Ptr pkt6 = IfaceMgr::instance().receive6(timeout);
@@ -444,7 +551,7 @@ TestControl::receivePackets() {
                 receiving  = false;
             } else {
                 if (pkt6->unpack()) {
-                    receivePacket6(pkt6);
+                    receivePacket6(socket, pkt6);
                 }
             }
         }
@@ -524,7 +631,7 @@ TestControl::run() {
     }
     registerOptionFactories();
     TestControlSocket socket(openSocket());
-    
+
     initializeStatsMgr();
     uint64_t packets_sent = 0;
     for (;;) {
@@ -534,7 +641,7 @@ TestControl::run() {
         }
         uint64_t packets_due = getNextExchangesNum();
 
-        receivePackets();
+        receivePackets(socket);
 
         for (uint64_t i = packets_due; i > 0; --i) {
             if (options.getIpVersion() == 4) {
@@ -555,8 +662,8 @@ TestControl::sendDiscover4(const TestControlSocket& socket) {
     // Generate the MAC address to be passed in the packet.
     std::vector<uint8_t> mac_address = generateMacAddress();
     // Generate trasnaction id to be set for the new exchange.
-    const uint32_t transid = static_cast<uint32_t>(random() % 0x00FFFFFF);
-    boost::shared_ptr<Pkt4> pkt4(new Pkt4(DHCPDISCOVER, transid));
+    const uint32_t transid = generateTransid();
+    Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, transid));
     if (!pkt4) {
         isc_throw(Unexpected, "failed to create DISCOVER packet");
     }
@@ -581,6 +688,59 @@ TestControl::sendDiscover4(const TestControlSocket& socket) {
 }
 
 void
+TestControl::sendRequest6(const TestControlSocket& socket,
+                          const Pkt6Ptr& solicit_pkt6,
+                          const Pkt6Ptr& advertise_pkt6) {
+    const uint32_t transid = static_cast<uint32_t>(random() % 0x00FFFFFF);
+    Pkt6Ptr pkt6(new Pkt6(DHCPV6_REQUEST, transid));
+    // Calculate elapsed time
+    ptime solicit_time = solicit_pkt6->getTimestamp();
+    ptime advertise_time = advertise_pkt6->getTimestamp();
+    if (solicit_time.is_not_a_date_time()) {
+        isc_throw(Unexpected, "timestamp was not set for SOLICIT packet");
+    }
+    if (advertise_time.is_not_a_date_time()) {
+        isc_throw(Unexpected, "timestamp was not set for ADVERTISE packet");
+    }
+    time_period period(solicit_time, advertise_time);
+    if (period.is_null()) {
+        pkt6->addOption(Option::factory(Option::V6, D6O_ELAPSED_TIME));
+    } else {
+        OptionBuffer buf();
+        const uint32_t elapsed_time = period.length().total_seconds();
+        OptionPtr opt_elapsed_time =
+            Option::factory(Option::V6, D6O_ELAPSED_TIME);
+        opt_elapsed_time->setUint16(static_cast<uint16_t>(elapsed_time));
+        pkt6->addOption(opt_elapsed_time);
+    }
+    OptionPtr opt_clientid = advertise_pkt6->getOption(D6O_CLIENTID);
+    if (!opt_clientid) {
+        isc_throw(Unexpected, "client id not found in received packet");
+    }
+    pkt6->addOption(opt_clientid);
+    OptionPtr opt_serverid = advertise_pkt6->getOption(D6O_SERVERID);
+    if (!opt_serverid) {
+        isc_throw(Unexpected, "server id not found in received packet");
+    }
+    pkt6->addOption(opt_serverid);
+    OptionPtr opt_ia_na = advertise_pkt6->getOption(D6O_IA_NA);
+    if (!opt_ia_na) {
+        isc_throw(Unexpected, "DHCPv6 IA_NA option not found in received "
+                  "packet");
+    }
+    pkt6->addOption(opt_ia_na);
+    setDefaults6(socket, pkt6);
+
+    pkt6->pack();
+    IfaceMgr::instance().send(pkt6);
+    if (!stats_mgr6_) {
+        isc_throw(InvalidOperation, "Statistics Manager for DHCPv6 "
+                  "hasn't been initialized");
+    }
+    stats_mgr6_->passSentPacket(StatsMgr6::XCHG_RR, pkt6);
+}
+
+void
 TestControl::sendSolicit6(const TestControlSocket& socket) {
     ++sent_packets_0_;
     last_sent_ = microsec_clock::universal_time();
@@ -589,13 +749,13 @@ TestControl::sendSolicit6(const TestControlSocket& socket) {
     // Generate DUID to be passed to the packet
     std::vector<uint8_t> duid = generateDuid();
     // Generate trasnaction id to be set for the new exchange.
-    const uint32_t transid = static_cast<uint32_t>(random() % 0x00FFFFFF);
-    boost::shared_ptr<Pkt6> pkt6(new Pkt6(DHCPV6_SOLICIT, transid));
+    const uint32_t transid = generateTransid();
+    Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, transid));
     if (!pkt6) {
         isc_throw(Unexpected, "failed to create SOLICIT packet");
     }
     pkt6->addOption(Option::factory(Option::V6, D6O_ELAPSED_TIME));
-    pkt6->addOption(Option::factory(Option::V6, D6O_RAPID_COMMIT));
+    //    pkt6->addOption(Option::factory(Option::V6, D6O_RAPID_COMMIT));
     pkt6->addOption(Option::factory(Option::V6, D6O_CLIENTID, duid));
     pkt6->addOption(Option::factory(Option::V6, D6O_ORO));
     pkt6->addOption(Option::factory(Option::V6, D6O_IA_NA));
@@ -612,7 +772,7 @@ TestControl::sendSolicit6(const TestControlSocket& socket) {
 
 void
 TestControl::setDefaults4(const TestControlSocket& socket,
-                          const boost::shared_ptr<Pkt4>& pkt) {
+                          const Pkt4Ptr& pkt) {
     CommandOptions& options = CommandOptions::instance();
     // Interface name.
     pkt->setIface(socket.getIface());
@@ -634,7 +794,7 @@ TestControl::setDefaults4(const TestControlSocket& socket,
 
 void
 TestControl::setDefaults6(const TestControlSocket& socket,
-                          const boost::shared_ptr<Pkt6>& pkt) {
+                          const Pkt6Ptr& pkt) {
     CommandOptions& options = CommandOptions::instance();
     // Interface name.
     pkt->setIface(socket.getIface());

+ 124 - 8
tests/tools/perfdhcp/test_control.h

@@ -20,6 +20,7 @@
 
 #include <boost/noncopyable.hpp>
 #include <boost/shared_ptr.hpp>
+#include <boost/function.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
 
 #include <dhcp/dhcp6.h>
@@ -55,6 +56,8 @@ public:
     typedef StatsMgr<dhcp::Pkt6> StatsMgr6;
     // Pointer to Statistics Manager for DHCPv6.
     typedef boost::shared_ptr<StatsMgr6> StatsMgr6Ptr;
+    // Packet exchange type.
+    typedef StatsMgr<>::ExchangeType ExchangeType;
 
     /// \brief Socket wrapper class.
     ///
@@ -120,6 +123,26 @@ public:
         asiolink::IOAddress addr_; ///< Address bound.
     };
 
+    /// \brief Default transaction id generator class.
+    ///
+    /// This is default transaction id generator class. The member
+    /// function is used to generate unique transaction id value.
+    /// Other generator classes should derive from this one to
+    /// override the standard generation algorithm (e.g. unit tests
+    /// override this class wih algorithm that produces more predictable
+    /// transaction id values).
+    class TransidGenerator {
+    public:
+        /// \brief generate transaction id.
+        ///
+        /// \return generated transazction id value.
+        virtual uint32_t generate() {
+            return static_cast<uint32_t>(random() % 0x00FFFFFF);
+        }
+    };
+
+    typedef boost::shared_ptr<TransidGenerator> TransidGeneratorPtr;
+
     /// \brief Length of the Ethernet HW address (MAC) in bytes.
     static const uint8_t HW_ETHER_LEN = 6;
 
@@ -129,7 +152,7 @@ public:
     /// \return the only existing instance of test control
     static TestControl& instance();
 
-    /// Run performance test.
+    /// brief\ Run performance test.
     ///
     /// Method runs whole performance test. Command line options must
     /// be parsed prior to running this function. Othewise function will
@@ -139,6 +162,14 @@ public:
     /// \throw isc::Unexpected if internal Test Controler error occured.
     void run();
 
+    /// \brief Set new transaction id generator.
+    ///
+    /// \param generator generator object to be used.
+    void setTransidGenerator(TransidGeneratorPtr& generator) {
+        transid_gen_.reset();
+        transid_gen_ = generator;
+    }
+
 protected:
 
     // We would really like these methods and members to be private but
@@ -285,6 +316,13 @@ protected:
     /// \return generated MAC address.
     std::vector<uint8_t> generateMacAddress() const;
 
+    /// \brief generate transaction id.
+    ///
+    /// \return generated transaction id.
+    uint32_t generateTransid() {
+        return(transid_gen_->generate());
+    }
+
     /// \brief Returns number of exchanges to be started.
     ///
     /// Method returns number of new exchanges to be started as soon
@@ -329,14 +367,45 @@ protected:
     /// not initialized.
     void printStats() const;
 
-    void receivePacket4(dhcp::Pkt4Ptr& pkt4);
+    /// \brief Receive DHCPv4 packet.
+    ///
+    /// Method performs reception of the DHCPv4 packet, updates
+    /// statistics and responsds to the server if required, e.g.
+    /// when OFFER packet arrives, this function will initiate
+    /// REQUEST message to the server.
+    ///
+    /// \param socket socket to be used.
+    /// \param pkt4 object representing DHCPv4 packet received.
+    /// \throw isc::BadValue if unknown message type received.
+    /// \throw isc::Unexpected if unexpected error occured.
+    void receivePacket4(const TestControlSocket& socket,
+                        const dhcp::Pkt4Ptr& pkt4);
 
-    void receivePacket6(dhcp::Pkt6Ptr& pkt4);
+    /// \brief Receive DHCPv6 packet.
+    ///
+    /// Method performs reception of the DHCPv6 packet, updates
+    /// statistics and responsds to the server if required, e.g.
+    /// when ADVERTISE packet arrives, this function will initiate
+    /// REQUEST message to the server.
+    ///
+    /// \param socket socket to be used.
+    /// \param pkt6 object representing DHCPv6 packet received.
+    /// \throw isc::BadValue if unknown message type received.
+    /// \throw isc::Unexpected if unexpected error occured.
+    void receivePacket6(const TestControlSocket& socket,
+                        const dhcp::Pkt6Ptr& pkt6);
 
     /// \brief Receive DHCPv4 or DHCPv6 packets from the server.
     ///
     /// Method receives DHCPv4 or DHCPv6 packets from the server.
-    void receivePackets();
+    /// This function will call \ref receivePacket4 or
+    /// \ref receivePacket6 depending if DHCPv4 or DHCPv6 packet
+    /// has arrived.
+    ///
+    /// \param socket socket to be used.
+    /// \throw::BadValue if unknown message type received.
+    /// \throw::Unexpected if unexpected error occured.
+    void receivePackets(const TestControlSocket& socket);
 
     /// \brief Register option factory functions for DHCPv4
     ///
@@ -378,6 +447,28 @@ protected:
     /// \throw isc::BadValue if MAC address has invalid length.
     void sendDiscover4(const TestControlSocket& socket);
 
+    /// \brief Sent DHCPv6 REQUEST message.
+    ///
+    /// Method creates and sends DHCPv6 REQUEST message to the server
+    /// with the following options:
+    /// - D6O_ELAPSED_TIME
+    /// - D6O_CLIENTID
+    /// - D6O_SERVERID
+    /// The elapsed time is calculated based on the duration between
+    /// sending a SOLICIT and receiving the ADVERTISE packet prior.
+    /// For this reason both solicit and advertise packet objects have
+    /// to be passed when calling this function.
+    ///
+    /// \param socket socket to be used to send message.
+    /// \param solicit_pkt6 SOLICIT packet object.
+    /// \param advertise_pkt6 ADVERTISE packet object.
+    /// \throw isc::Unexpected if unexpected error occured.
+    /// \throw isc::InvalidOperation if Statistics Manager has not been
+    /// initialized.
+    void sendRequest6(const TestControlSocket& socket,
+                      const dhcp::Pkt6Ptr& solicit_pkt6,
+                      const dhcp::Pkt6Ptr& advertise_pkt6);
+
     /// \brief Send DHCPv6 SOLICIT message.
     ///
     /// Method creates and sends DHCPv6 SOLICIT message to the server
@@ -405,7 +496,7 @@ protected:
     /// \param socket socket used to send the packet.
     /// \param pkt reference to packet to be configured.
     void setDefaults4(const TestControlSocket& socket,
-                      const boost::shared_ptr<dhcp::Pkt4>& pkt);
+                      const dhcp::Pkt4Ptr& pkt);
 
     /// \brief Set default DHCPv6 packet parameters.
     ///
@@ -420,7 +511,7 @@ protected:
     /// \param socket socket used to send the packet.
     /// \param pkt reference to packet to be configured.
     void setDefaults6(const TestControlSocket& socket,
-                      const boost::shared_ptr<dhcp::Pkt6>& pkt);
+                      const dhcp::Pkt6Ptr& pkt);
 
     /// \brief Update due time to initiate next chunk of exchanges.
     ///
@@ -431,13 +522,38 @@ protected:
 
 private:
 
+    /// \brief Generate transaction id using random function.
+    ///
+    /// \return generated transaction id value.
+    static uint32_t generateTransidRandom();
+
+    /// \brief Get number of received packets.
+    ///
+    /// Get the number of received packets from the Statistics Manager.
+    /// Function may throw if Statistics Manager object is not
+    /// initialized.
+    /// \param xchg_type packet exchange type.
+    /// \return number of received packets.
+    uint64_t getRcvdPacketsNum(const ExchangeType xchg_type) const;
+
+    /// \brief Get number of sent packets.
+    ///
+    /// Get the number of sent packets from the Statistics Manager.
+    /// Function may throw if Statistics Manager object is not
+    /// initialized.
+    /// \param xchg_type packet exchange type.
+    /// \return number of sent packets.
+    uint64_t getSentPacketsNum(const ExchangeType xchg_type) 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.
+    StatsMgr4Ptr stats_mgr4_;  ///< Statistics Manager 4.
+    StatsMgr6Ptr stats_mgr6_;  ///< Statistics Manager 6.
 
-    StatsMgr4Ptr stats_mgr4_;  /// Statistics Manager 4.
-    StatsMgr6Ptr stats_mgr6_;  /// Statistics Manager 6.
+    // Pointers to functions.
+    TransidGeneratorPtr transid_gen_; ///< Transaction id generator.
 
     uint64_t sent_packets_0_;
     uint64_t sent_packets_1_;

+ 235 - 2
tests/tools/perfdhcp/tests/test_control_unittest.cc

@@ -38,6 +38,34 @@ using namespace isc::perfdhcp;
 /// to allow unit testing.
 class NakedTestControl: public TestControl {
 public:
+
+    /// \brief Incremental transaction id generaator.
+    ///
+    /// This is incremental transaction id generator. It overrides
+    /// the default transaction id generator that generates transaction
+    /// ids using random function. This generator will generate values
+    /// like: 1,2,3 etc.
+    class IncrementalGenerator : public TestControl::TransidGenerator {
+    public:
+        /// \brief Default constructor.
+        IncrementalGenerator() :
+            TransidGenerator(),
+            transid_(0) {
+        }
+
+        /// \brief Generate unique transaction id.
+        ///
+        /// Generate unique transaction ids incrementally:
+        /// 1,2,3,4 etc.
+        ///
+        /// \return generated transaction id.
+        virtual uint32_t generate() {
+            return ++transid_;
+        }
+    private:
+        uint32_t transid_; ///< Last generated transaction id.
+    };
+
     using TestControl::checkExitConditions;
     using TestControl::factoryElapsedTime6;
     using TestControl::factoryGeneric;
@@ -48,14 +76,15 @@ public:
     using TestControl::generateDuid;
     using TestControl::generateMacAddress;
     using TestControl::getNextExchangesNum;
+    using TestControl::initializeStatsMgr;
     using TestControl::openSocket;
-    using TestControl::receivePackets;
+    using TestControl::receivePacket4;
+    using TestControl::receivePacket6;
     using TestControl::registerOptionFactories;
     using TestControl::sendDiscover4;
     using TestControl::sendSolicit6;
     using TestControl::setDefaults4;
     using TestControl::setDefaults6;
-    using TestControl::updateSendDue;
 
     NakedTestControl() : TestControl() { };
 
@@ -78,6 +107,11 @@ public:
     /// \brief Default Constructor
     TestControlTest() { }
 
+    static uint32_t generateTransidIncremental() {
+        static uint32_t transid(1);
+        return ++transid;
+    }
+
     /// \brief Get local loopback interface name.
     ///
     /// Scan available network interfaces for local loopback
@@ -260,6 +294,107 @@ public:
         }
     }
 
+    /// \brief Test DHCPv4 exchanges.
+    ///
+    /// Function simulates DHCPv4 exchanges. Function caller specifies
+    /// number of exchanges to be simulated and number of simulated
+    /// responses. When number of responses is lower than number of
+    /// iterations than the difference between them is the number
+    /// of simulated packet drops. This is useful to test if program
+    /// exit conditions are handled properly (maximum number of packet
+    /// drops specified as -D<max-drops> is taken into account).
+    ///
+    /// \param iterations_num number of exchanges to simulate.
+    /// \param receive_num number of received OFFER packets.
+    /// \param iterations_performed actual number of iterations.
+    void testPkt4Exchange(int iterations_num,
+                          int receive_num,
+                          int& iterations_performed) const {
+        uint16_t port = 10547;
+        int sock_handle = 0;
+        NakedTestControl tc;
+        tc.initializeStatsMgr();
+        // Incremental transaction id generator will generate
+        // predictable values of transaction id for each iteration.
+        // This is important because we need to simulate reponses
+        // from the server and use the same transaction ids as in
+        // packets sent by client.
+        TestControl::TransidGeneratorPtr
+            generator(new NakedTestControl::IncrementalGenerator());
+        tc.setTransidGenerator(generator);
+        // Socket is needed to send packets through the interface.
+        ASSERT_NO_THROW(sock_handle = tc.openSocket(port));
+        TestControl::TestControlSocket sock(sock_handle);
+        int i = 0;
+        for (; i < iterations_num; ++i) {
+            if (tc.checkExitConditions()) {
+                break;
+            }
+            ASSERT_NO_THROW(tc.sendDiscover4(sock));
+            // Do not simulate responses for packets later
+            // that specified as receive_num. This simulates
+            // packet drops.
+            if (i - 1 < receive_num) {
+                boost::shared_ptr<Pkt4> offer_pkt4(createOfferPkt4(i));
+                // Receive OFFER and send REQUEST.
+                ASSERT_NO_THROW(tc.receivePacket4(sock, offer_pkt4));
+            }
+        }
+        // Return the number of iterations performed.
+        iterations_performed = i;
+    }
+
+    /// \brief Test DHCPv6 exchanges.
+    ///
+    /// Function simulates DHCPv6 exchanges. Function caller specifies
+    /// number of exchanges to be simulated and number of simulated
+    /// responses. When number of responses is lower than number of
+    /// iterations than the difference between them is the number
+    /// of simulated packet drops. This is useful to test if program
+    /// exit conditions are handled properly (maximum number of packet
+    /// drops specified as -D<max-drops> is taken into account).
+    ///
+    /// \param iterations_num number of exchanges to simulate.
+    /// \param receive_num number of received OFFER packets.
+    /// \param iterations_performed actual number of iterations.
+    void testPkt6Exchange(int iterations_num,
+                          int receive_num,
+                          int& iterations_performed) const {
+        uint16_t port = 10547;
+        int sock_handle = 0;
+        NakedTestControl tc;
+        tc.initializeStatsMgr();
+        // Incremental transaction id generator will generate
+        // predictable values of transaction id for each iteration.
+        // This is important because we need to simulate reponses
+        // from the server and use the same transaction ids as in
+        // packets sent by client.
+        TestControl::TransidGeneratorPtr
+            generator(new NakedTestControl::IncrementalGenerator());
+        tc.setTransidGenerator(generator);
+        // Socket is needed to send packets through the interface.
+        ASSERT_NO_THROW(sock_handle = tc.openSocket(port));
+        TestControl::TestControlSocket sock(sock_handle);
+        int i = 0;
+        for (; i < iterations_num; ++i) {
+            if (tc.checkExitConditions()) {
+                break;
+            }
+            // Do not simulate responses for packets later
+            // that specified as receive_num. This simulates
+            // packet drops.
+            ASSERT_NO_THROW(tc.sendSolicit6(sock));
+            if (i - 1 < receive_num) {
+                boost::shared_ptr<Pkt6> advertise_pkt6(createAdvertisePkt6(i));
+                // Receive ADVERTISE and send REQUEST.
+                ASSERT_NO_THROW(tc.receivePacket6(sock, advertise_pkt6));
+            }
+
+        }
+        // Return the number of iterations performed.
+        iterations_performed = i;
+    }
+
     /// \brief Test generation of multiple MAC addresses.
     ///
     /// This method validates generation of multiple MAC addresses.
@@ -322,6 +457,33 @@ public:
         CommandOptionsHelper::process(cmdline);
     }
 
+private:
+    boost::shared_ptr<Pkt4>
+    createOfferPkt4(uint32_t transid) const {
+        boost::shared_ptr<Pkt4> offer(new Pkt4(DHCPOFFER, transid));
+        OptionPtr opt_msg_type = Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE,
+                                                 OptionBuffer(DHCPOFFER));
+        offer->setYiaddr(asiolink::IOAddress("127.0.0.1"));
+        offer->addOption(opt_msg_type);
+        offer->updateTimestamp();
+        return(offer);
+    }
+
+    boost::shared_ptr<Pkt6>
+    createAdvertisePkt6(uint32_t transid) const {
+        OptionPtr opt_ia_na = Option::factory(Option::V6, D6O_IA_NA);
+        OptionPtr opt_serverid(new Option(Option::V6, D6O_SERVERID));
+        NakedTestControl tc;
+        std::vector<uint8_t> duid(tc.generateDuid());
+        OptionPtr opt_clientid(Option::factory(Option::V6, D6O_CLIENTID, duid));
+        boost::shared_ptr<Pkt6> advertise(new Pkt6(DHCPV6_ADVERTISE, transid));
+        advertise->addOption(opt_ia_na);
+        advertise->addOption(opt_serverid);
+        advertise->addOption(opt_clientid);
+        advertise->updateTimestamp();
+        return(advertise);
+    }
+
 };
 
 TEST_F(TestControlTest, GenerateDuid) {
@@ -568,6 +730,77 @@ TEST_F(TestControlTest, Packet6) {
     }
 }
 
+TEST_F(TestControlTest, Packet4Exchange) {
+    // Get the local loopback interface to open socket on
+    // it and test packets exchanges. We don't want to fail
+    // the test if interface is not available.
+    std::string loopback_iface(getLocalLoopback());
+    if (loopback_iface.empty()) {
+        std::cout << "Unable to find the loopback interface. Skip test."
+                  << std::endl;
+        return;
+    }
+
+    // Set number of iterations to some high value.
+    const int iterations_num = 100;
+    processCmdLine("perfdhcp -l " + loopback_iface
+                   + " -r 100 -n 10 -R 20 127.0.0.1");
+    // The actual number of iterations will be stored in the
+    // following variable.
+    int iterations_performed = 0;
+    testPkt4Exchange(iterations_num, iterations_num, iterations_performed);
+    // The command line restricts the number of iterations to 10
+    // with -n 10 parameter.
+    EXPECT_EQ(10, iterations_performed);
+
+    // With the following command line we restrict the maximum
+    // number of dropped packets to 20% of all.
+    processCmdLine("perfdhcp -l " + loopback_iface
+                   + " -r 100 -R 20 -n 20 -D 10% 127.0.0.1");
+    // The number iterations is restricted by the percentage of
+    // dropped packets (-D 10%). We also have to bump up the number
+    // of iterations because the percentage limitation checks starts
+    // at packet #10. We expect that at packet #12 the 10% threshold
+    // will be reached.
+    const int received_num = 10;
+    testPkt4Exchange(iterations_num, received_num, iterations_performed);
+    EXPECT_EQ(12, iterations_performed);
+}
+
+TEST_F(TestControlTest, Packet6Exchange) {
+    // Get the local loopback interface to open socket on
+    // it and test packets exchanges. We don't want to fail
+    // the test if interface is not available.
+    std::string loopback_iface(getLocalLoopback());
+    if (loopback_iface.empty()) {
+        std::cout << "Unable to find the loopback interface. Skip test."
+                  << std::endl;
+        return;
+    }
+
+    const int iterations_num = 100;
+    // Set number of iterations to 10.
+    processCmdLine("perfdhcp -l " + loopback_iface
+                   + " -6 -r 100 -n 10 -R 20 ::1");
+    int iterations_performed = 0;
+    // Set number of received packets equal to number of iterations.
+    // This simulates no packet drops.
+    testPkt6Exchange(iterations_num, iterations_num, iterations_performed);
+    // Actual number of iterations should be 10.
+    EXPECT_EQ(10, iterations_performed);
+
+    // The maximum number of dropped packets is 3 (because of -D 3).
+    processCmdLine("perfdhcp -l " + loopback_iface
+                   + " -6 -r 100 -n 10 -R 20 -D 3 ::1");
+    // For the first 3 packets we are simulating responses from server.
+    // For other packets we don't so packet as 4,5,6 will be dropped and
+    // then test should be interrupted and actual number of iterations will
+    // be 6.
+    const int received_num = 3;
+    testPkt6Exchange(iterations_num, received_num, iterations_performed);
+    EXPECT_EQ(6, iterations_performed);
+}
+
 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