Browse Source

Merge branch 'trac1959'

Marcin Siodelski 12 years ago
parent
commit
a8cf043db8
34 changed files with 5019 additions and 348 deletions
  1. 1 0
      configure.ac
  2. 57 24
      src/lib/dhcp/iface_mgr.cc
  3. 21 2
      src/lib/dhcp/iface_mgr.h
  4. 26 0
      src/lib/dhcp/libdhcp++.cc
  5. 22 2
      src/lib/dhcp/libdhcp++.h
  6. 8 0
      src/lib/dhcp/option.cc
  7. 36 0
      src/lib/dhcp/option.h
  8. 104 0
      src/lib/dhcp/tests/iface_mgr_unittest.cc
  9. 87 0
      src/lib/dhcp/tests/libdhcp++_unittest.cc
  10. 17 15
      tests/tools/perfdhcp/Makefile.am
  11. 187 11
      tests/tools/perfdhcp/command_options.cc
  12. 133 114
      tests/tools/perfdhcp/command_options.h
  13. 68 41
      tests/tools/perfdhcp/localized_option.h
  14. 50 0
      tests/tools/perfdhcp/main.cc
  15. 9 1
      tests/tools/perfdhcp/perf_pkt4.cc
  16. 26 0
      tests/tools/perfdhcp/perf_pkt4.h
  17. 8 0
      tests/tools/perfdhcp/perf_pkt6.cc
  18. 26 0
      tests/tools/perfdhcp/perf_pkt6.h
  19. 7 1
      tests/tools/perfdhcp/pkt_transform.cc
  20. 30 0
      tests/tools/perfdhcp/pkt_transform.h
  21. 119 35
      tests/tools/perfdhcp/stats_mgr.h
  22. 8 0
      tests/tools/perfdhcp/templates/Makefile.am
  23. 1 0
      tests/tools/perfdhcp/templates/discover-example.hex
  24. 1 0
      tests/tools/perfdhcp/templates/request4-example.hex
  25. 1 0
      tests/tools/perfdhcp/templates/request6-example.hex
  26. 1 0
      tests/tools/perfdhcp/templates/solicit-example.hex
  27. 1682 0
      tests/tools/perfdhcp/test_control.cc
  28. 803 0
      tests/tools/perfdhcp/test_control.h
  29. 3 0
      tests/tools/perfdhcp/tests/Makefile.am
  30. 138 0
      tests/tools/perfdhcp/tests/command_options_helper.h
  31. 280 100
      tests/tools/perfdhcp/tests/command_options_unittest.cc
  32. 46 0
      tests/tools/perfdhcp/tests/perf_pkt4_unittest.cc
  33. 2 2
      tests/tools/perfdhcp/tests/stats_mgr_unittest.cc
  34. 1011 0
      tests/tools/perfdhcp/tests/test_control_unittest.cc

+ 1 - 0
configure.ac

@@ -1223,6 +1223,7 @@ AC_CONFIG_FILES([Makefile
                  tests/tools/badpacket/tests/Makefile
                  tests/tools/perfdhcp/Makefile
                  tests/tools/perfdhcp/tests/Makefile
+                 tests/tools/perfdhcp/templates/Makefile
                  dns++.pc
                ])
 AC_OUTPUT([doc/version.ent

+ 57 - 24
src/lib/dhcp/iface_mgr.cc

@@ -50,6 +50,15 @@ IfaceMgr::Iface::Iface(const std::string& name, int ifindex)
     memset(mac_, 0, sizeof(mac_));
 }
 
+void
+IfaceMgr::Iface::closeSockets() {
+    for (SocketCollection::iterator sock = sockets_.begin();
+         sock != sockets_.end(); ++sock) {
+        close(sock->sockfd_);
+    }
+    sockets_.clear();
+}
+
 std::string
 IfaceMgr::Iface::getFullName() const {
     ostringstream tmp;
@@ -138,15 +147,8 @@ IfaceMgr::IfaceMgr()
 void IfaceMgr::closeSockets() {
     for (IfaceCollection::iterator iface = ifaces_.begin();
          iface != ifaces_.end(); ++iface) {
-
-        for (SocketCollection::iterator sock = iface->sockets_.begin();
-             sock != iface->sockets_.end(); ++sock) {
-            cout << "Closing socket " << sock->sockfd_ << endl;
-            close(sock->sockfd_);
-        }
-        iface->sockets_.clear();
+        iface->closeSockets();
     }
-
 }
 
 IfaceMgr::~IfaceMgr() {
@@ -477,11 +479,34 @@ IfaceMgr::getLocalAddress(const IOAddress& remote_addr, const uint16_t port) {
     asio::io_service io_service;
     asio::ip::udp::socket sock(io_service);
 
-    // Try to connect to remote endpoint and check if attempt is successful.
     asio::error_code err_code;
+    // If remote address is broadcast address we have to
+    // allow this on the socket.
+    if (remote_addr.getAddress().is_v4() &&
+        (remote_addr == IOAddress("255.255.255.255"))) {
+        // Socket has to be open prior to setting the broadcast
+        // option. Otherwise set_option will complain about
+        // bad file descriptor.
+
+        // @todo: We don't specify interface in any way here. 255.255.255.255
+        // We can very easily end up with a socket working on a different
+        // interface.
+        sock.open(asio::ip::udp::v4(), err_code);
+        if (err_code) {
+            isc_throw(Unexpected, "failed to open UDPv4 socket");
+        }
+        sock.set_option(asio::socket_base::broadcast(true), err_code);
+        if (err_code) {
+            sock.close();
+            isc_throw(Unexpected, "failed to enable broadcast on the socket");
+        }
+    }
+
+    // Try to connect to remote endpoint and check if attempt is successful.
     sock.connect(remote_endpoint->getASIOEndpoint(), err_code);
     if (err_code) {
-        isc_throw(Unexpected,"Failed to connect to remote endpoint.");
+        sock.close();
+        isc_throw(Unexpected,"failed to connect to remote endpoint.");
     }
 
     // Once we are connected socket object holds local endpoint.
@@ -489,6 +514,9 @@ IfaceMgr::getLocalAddress(const IOAddress& remote_addr, const uint16_t port) {
         sock.local_endpoint();
     asio::ip::address local_address(local_endpoint.address());
 
+    // Close the socket.
+    sock.close();
+
     // Return address of local endpoint.
     return IOAddress(local_address);
 }
@@ -546,8 +574,9 @@ int IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port) {
     memset(&addr6, 0, sizeof(addr6));
     addr6.sin6_family = AF_INET6;
     addr6.sin6_port = htons(port);
-    if (addr.toText() != "::1")
-      addr6.sin6_scope_id = if_nametoindex(iface.getName().c_str());
+    if (addr.toText() != "::1") {
+        addr6.sin6_scope_id = if_nametoindex(iface.getName().c_str());
+    }
 
     memcpy(&addr6.sin6_addr,
            addr.getAddress().to_v6().to_bytes().data(),
@@ -724,7 +753,6 @@ IfaceMgr::send(const Pkt6Ptr& pkt) {
 bool
 IfaceMgr::send(const Pkt4Ptr& pkt)
 {
-
     Iface* iface = getIface(pkt->getIface());
     if (!iface) {
         isc_throw(BadValue, "Unable to send Pkt4. Invalid interface ("
@@ -800,8 +828,9 @@ IfaceMgr::receive4(uint32_t timeout) {
     /// provided set to indicated which sockets have something to read.
     for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
 
-        for (SocketCollection::const_iterator s = iface->sockets_.begin();
-             s != iface->sockets_.end(); ++s) {
+        const SocketCollection& socket_collection = iface->getSockets();
+        for (SocketCollection::const_iterator s = socket_collection.begin();
+             s != socket_collection.end(); ++s) {
 
             // Only deal with IPv4 addresses.
             if (s->addr_.getFamily() == AF_INET) {
@@ -864,8 +893,9 @@ IfaceMgr::receive4(uint32_t timeout) {
 
     // Let's find out which interface/socket has the data
     for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
-        for (SocketCollection::const_iterator s = iface->sockets_.begin();
-             s != iface->sockets_.end(); ++s) {
+        const SocketCollection& socket_collection = iface->getSockets();
+        for (SocketCollection::const_iterator s = socket_collection.begin();
+             s != socket_collection.end(); ++s) {
             if (FD_ISSET(s->sockfd_, &sockets)) {
                 candidate = &(*s);
                 break;
@@ -967,9 +997,9 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout) {
     /// provided set to indicated which sockets have something to read.
     IfaceCollection::const_iterator iface;
     for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
-
-        for (SocketCollection::const_iterator s = iface->sockets_.begin();
-             s != iface->sockets_.end(); ++s) {
+        const SocketCollection& socket_collection = iface->getSockets();
+        for (SocketCollection::const_iterator s = socket_collection.begin();
+             s != socket_collection.end(); ++s) {
 
             // Only deal with IPv4 addresses.
             if (s->addr_.getFamily() == AF_INET6) {
@@ -1032,8 +1062,9 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout) {
 
     // Let's find out which interface/socket has the data
     for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
-        for (SocketCollection::const_iterator s = iface->sockets_.begin();
-             s != iface->sockets_.end(); ++s) {
+        const SocketCollection& socket_collection = iface->getSockets();
+        for (SocketCollection::const_iterator s = socket_collection.begin();
+             s != socket_collection.end(); ++s) {
             if (FD_ISSET(s->sockfd_, &sockets)) {
                 candidate = &(*s);
                 break;
@@ -1168,8 +1199,9 @@ uint16_t IfaceMgr::getSocket(const isc::dhcp::Pkt6& pkt) {
                   << pkt.getIface());
     }
 
+    const SocketCollection& socket_collection = iface->getSockets();
     SocketCollection::const_iterator s;
-    for (s = iface->sockets_.begin(); s != iface->sockets_.end(); ++s) {
+    for (s = socket_collection.begin(); s != socket_collection.end(); ++s) {
         if ((s->family_ == AF_INET6) &&
             (!s->addr_.getAddress().to_v6().is_multicast())) {
             return (s->sockfd_);
@@ -1190,8 +1222,9 @@ uint16_t IfaceMgr::getSocket(isc::dhcp::Pkt4 const& pkt) {
                   << pkt.getIface());
     }
 
+    const SocketCollection& socket_collection = iface->getSockets();
     SocketCollection::const_iterator s;
-    for (s = iface->sockets_.begin(); s != iface->sockets_.end(); ++s) {
+    for (s = socket_collection.begin(); s != socket_collection.end(); ++s) {
         if (s->family_ == AF_INET) {
             return (s->sockfd_);
         }

+ 21 - 2
src/lib/dhcp/iface_mgr.h

@@ -72,8 +72,10 @@ public:
     };
 
     /// type that holds a list of socket informations
+    /// @todo: Add SocketCollectionConstIter type
     typedef std::list<SocketInfo> SocketCollection;
 
+
     /// @brief represents a single network interface
     ///
     /// Iface structure represents network interface with all useful
@@ -89,6 +91,9 @@ public:
         /// @param ifindex interface index (unique integer identifier)
         Iface(const std::string& name, int ifindex);
 
+        /// @brief Closes all open sockets on interface.
+        void closeSockets();
+
         /// @brief Returns full interface name as "ifname/ifindex" string.
         ///
         /// @return string with interface name
@@ -192,11 +197,25 @@ public:
         /// @return true if there was such socket, false otherwise
         bool delSocket(uint16_t sockfd);
 
+        /// @brief Returns collection of all sockets added to interface.
+        ///
+        /// When new socket is created with @ref IfaceMgr::openSocket
+        /// it is added to sockets collection on particular interface.
+        /// If socket is opened by other means (e.g. function that does
+        /// not use @ref IfaceMgr::openSocket) it will not be available
+        /// in this collection. Note that functions like
+        /// @ref IfaceMgr::openSocketFromIface use
+        /// @ref IfaceMgr::openSocket internally.
+        /// The returned reference is only valid during the lifetime of
+        /// the IfaceMgr object that returned it.
+        ///
+        /// @return collection of sockets added to interface
+        const SocketCollection& getSockets() const { return sockets_; }
+
+    protected:
         /// socket used to sending data
-        /// TODO: this should be protected
         SocketCollection sockets_;
 
-    protected:
         /// network interface name
         std::string name_;
 

+ 26 - 0
src/lib/dhcp/libdhcp++.cc

@@ -15,6 +15,7 @@
 #include <boost/shared_array.hpp>
 #include <boost/shared_ptr.hpp>
 #include <util/buffer.h>
+#include <exceptions/exceptions.h>
 #include <dhcp/libdhcp++.h>
 #include "config.h"
 #include <dhcp/dhcp4.h>
@@ -34,6 +35,31 @@ std::map<unsigned short, Option::Factory*> LibDHCP::v4factories_;
 std::map<unsigned short, Option::Factory*> LibDHCP::v6factories_;
 
 
+OptionPtr
+LibDHCP::optionFactory(Option::Universe u,
+                       uint16_t type,
+                       const OptionBuffer& buf) {
+    FactoryMap::iterator it;
+    if (u == Option::V4) {
+        it = v4factories_.find(type);
+        if (it == v4factories_.end()) {
+            isc_throw(BadValue, "factory function not registered "
+            "for DHCP v4 option type " << type);
+        }
+    } else if (u == Option::V6) {
+        it = v6factories_.find(type);
+        if (it == v6factories_.end()) {
+            isc_throw(BadValue, "factory function not registered "
+                      "for DHCPv6 option type " << type);
+        }
+    } else {
+        isc_throw(BadValue, "invalid universe specified (expected "
+                  "Option::V4 or Option::V6");
+    }
+    return (it->second(u, type, buf));
+}
+
+
 size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
                                isc::dhcp::Option::OptionCollection& options) {
     size_t offset = 0;

+ 22 - 2
src/lib/dhcp/libdhcp++.h

@@ -25,6 +25,26 @@ namespace dhcp {
 class LibDHCP {
 
 public:
+
+    /// Map of factory functions.
+    typedef std::map<unsigned short, Option::Factory*>  FactoryMap;
+
+    /// @brief Factory function to create instance of option.
+    ///
+    /// Factory method creates instance of specified option. The option
+    /// to be created has to have corresponding factory function
+    /// registered with \ref LibDHCP::OptionFactoryRegister.
+    ///
+    /// @param u universe of the option (V4 or V6)
+    /// @param type option-type
+    /// @param buf option-buffer
+    /// @throw isc::InvalidOperation if there is no factory function
+    /// registered for specified option type.
+    /// @return instance of option.
+    static isc::dhcp::OptionPtr optionFactory(isc::dhcp::Option::Universe u,
+                                              uint16_t type,
+                                              const OptionBuffer& buf);
+
     /// Builds collection of options.
     ///
     /// Builds raw (on-wire) data for provided collection of options.
@@ -84,10 +104,10 @@ public:
                                       Option::Factory * factory);
 protected:
     /// pointers to factories that produce DHCPv6 options
-    static std::map<unsigned short, Option::Factory*> v4factories_;
+    static FactoryMap v4factories_;
 
     /// pointers to factories that produce DHCPv6 options
-    static std::map<unsigned short, Option::Factory*> v6factories_;
+    static FactoryMap v6factories_;
 };
 
 }

+ 8 - 0
src/lib/dhcp/option.cc

@@ -29,6 +29,14 @@ using namespace isc::util;
 namespace isc {
 namespace dhcp {
 
+OptionPtr
+Option::factory(Option::Universe u,
+        uint16_t type,
+        const OptionBuffer& buf) {
+    return(LibDHCP::optionFactory(u, type, buf));
+}
+
+
 Option::Option(Universe u, uint16_t type)
     :universe_(u), type_(type) {
 

+ 36 - 0
src/lib/dhcp/option.h

@@ -63,9 +63,45 @@ public:
     /// @param type option type
     /// @param buf pointer to a buffer
     ///
+    /// @todo Passing a separate buffer for each option means that a copy
+    ///       was done. We can avoid it by passing 2 iterators.
+    ///
     /// @return a pointer to a created option object
     typedef OptionPtr Factory(Option::Universe u, uint16_t type, const OptionBuffer& buf);
 
+    /// @brief Factory function to create instance of option.
+    ///
+    /// Factory method creates instance of specified option. The option
+    /// to be created has to have corresponding factory function
+    /// registered with \ref LibDHCP::OptionFactoryRegister.
+    ///
+    /// @param u universe of the option (V4 or V6)
+    /// @param type option-type
+    /// @param buf option-buffer
+    /// @throw isc::InvalidOperation if there is no factory function
+    /// registered for specified option type.
+    /// @return instance of option.
+    static OptionPtr factory(Option::Universe u,
+                             uint16_t type,
+                             const OptionBuffer& buf);
+
+    /// @brief Factory function to create instance of option.
+    ///
+    /// Factory method creates instance of specified option. The option
+    /// to be created has to have corresponding factory function
+    /// registered with \ref LibDHCP::OptionFactoryRegister.
+    /// This method creates empty \ref OptionBuffer object. Use this
+    /// factory function if it is not needed to pass custom buffer.
+    ///
+    /// @param u universe of the option (V4 or V6)
+    /// @param type option-type
+    /// @throw isc::InvalidOperation if there is no factory function
+    /// registered for specified option type.
+    /// @return instance of option.
+    static OptionPtr factory(Option::Universe u, uint16_t type) {
+        return factory(u, type, OptionBuffer());
+    }
+
     /// @brief ctor, used for options constructed, usually during transmission
     ///
     /// @param u option universe (DHCPv4 or DHCPv6)

+ 104 - 0
src/lib/dhcp/tests/iface_mgr_unittest.cc

@@ -59,6 +59,7 @@ public:
 
     ~IfaceMgrTest() {
     }
+
 };
 
 // We need some known interface to work reliably. Loopback interface
@@ -217,6 +218,94 @@ TEST_F(IfaceMgrTest, getIface) {
 
 }
 
+TEST_F(IfaceMgrTest, multipleSockets) {
+    boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+    // container for initialized socket descriptors
+    std::list<uint16_t> init_sockets;
+
+    // create socket #1
+    int socket1 = 0;
+    ASSERT_NO_THROW(
+        socket1 = ifacemgr->openSocketFromIface(LOOPBACK, PORT1, AF_INET);
+    );
+    ASSERT_GT(socket1, 0);
+    init_sockets.push_back(socket1);
+
+    // create socket #2
+    IOAddress loAddr("127.0.0.1");
+    int socket2 = 0;
+    ASSERT_NO_THROW(
+        socket2 = ifacemgr->openSocketFromRemoteAddress(loAddr, PORT2);
+    );
+    ASSERT_GT(socket2, 0);
+    init_sockets.push_back(socket2);
+
+    // Get loopback interface. If we don't find one we are unable to run
+    // this test but we don't want to fail.
+    IfaceMgr::Iface* iface_ptr = ifacemgr->getIface(LOOPBACK);
+    if (iface_ptr == NULL) {
+        cout << "Local loopback interface not found. Skipping test. " << endl;
+        return;
+    }
+    // Once sockets have been sucessfully opened, they are supposed to
+    // be on the list. Here we start to test if all expected sockets
+    // are on the list and no other (unexpected) socket is there.
+    IfaceMgr::SocketCollection sockets = iface_ptr->getSockets();
+    int matched_sockets = 0;
+    for (std::list<uint16_t>::iterator init_sockets_it =
+             init_sockets.begin();
+         init_sockets_it != init_sockets.end(); ++init_sockets_it) {
+        // Set socket descriptors non blocking in order to be able
+        // to call recv() on them without hang.
+        int flags = fcntl(*init_sockets_it, F_GETFL, 0);
+        ASSERT_GE(flags, 0);
+        ASSERT_GE(fcntl(*init_sockets_it, F_SETFL, flags | O_NONBLOCK), 0);
+        // recv() is expected to result in EWOULDBLOCK error on non-blocking
+        // socket in case socket is valid but simply no data are coming in.
+        char buf;
+        recv(*init_sockets_it, &buf, 1, MSG_PEEK);
+        EXPECT_EQ(EWOULDBLOCK, errno);
+        // Apart from the ability to use the socket we want to make
+        // sure that socket on the list is the one that we created.
+        for (IfaceMgr::SocketCollection::const_iterator socket_it =
+                 sockets.begin(); socket_it != sockets.end(); ++socket_it) {
+            if (*init_sockets_it == socket_it->sockfd_) {
+                // This socket is the one that we created.
+                ++matched_sockets;
+                break;
+            }
+        }
+    }
+    // all created sockets have been matched if this condition works.
+    EXPECT_EQ(sockets.size(), matched_sockets);
+
+    // closeSockets() is the other function that we want to test. It
+    // is supposed to close all sockets so as we will not be able to use
+    // them anymore communication.
+    ifacemgr->closeSockets();
+
+    // closed sockets are supposed to be removed from the list
+    sockets = iface_ptr->getSockets();
+    ASSERT_EQ(0, sockets.size());
+
+    // We are still in posession of socket descriptors that we created
+    // on the beginning of this test. We can use them to check whether
+    // closeSockets() only removed them from the list or they have been
+    // really closed.
+    for (std::list<uint16_t>::const_iterator init_sockets_it =
+             init_sockets.begin();
+         init_sockets_it != init_sockets.end(); ++init_sockets_it) {
+        // recv() must result in error when using invalid socket.
+        char buf;
+        recv(*init_sockets_it, &buf, 1, MSG_PEEK);
+        // EWOULDBLOCK would mean that socket is valid/open but
+        // simply no data is received so we have to check for
+        // other errors.
+        EXPECT_NE(EWOULDBLOCK, errno);
+    }
+}
+
 TEST_F(IfaceMgrTest, sockets6) {
     // testing socket operation in a portable way is tricky
     // without interface detection implemented
@@ -317,6 +406,21 @@ TEST_F(IfaceMgrTest, socketsFromRemoteAddress) {
     );
     EXPECT_GT(socket2, 0);
     close(socket2);
+
+    // The following test is currently disabled for OSes other than
+    // Linux because interface detection is not implemented on them.
+    // @todo enable this test for all OSes once interface detection
+    // is implemented.
+#if defined(OS_LINUX)
+    // Open v4 socket to connect to broadcast address.
+    int socket3  = 0;
+    IOAddress bcastAddr("255.255.255.255");
+    EXPECT_NO_THROW(
+        socket3 = ifacemgr->openSocketFromRemoteAddress(bcastAddr, PORT2);
+    );
+    EXPECT_GT(socket3, 0);
+    close(socket3);
+#endif
 }
 
 // TODO: disabled due to other naming on various systems

+ 87 - 0
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -18,6 +18,8 @@
 #include <arpa/inet.h>
 #include <gtest/gtest.h>
 #include <util/buffer.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
 #include <dhcp/libdhcp++.h>
 #include "config.h"
 
@@ -31,6 +33,19 @@ class LibDhcpTest : public ::testing::Test {
 public:
     LibDhcpTest() {
     }
+
+    /// @brief Generic factory function to create any option.
+    ///
+    /// Generic factory function to create any option.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param type option-type
+    /// @param buf option-buffer
+    static OptionPtr genericOptionFactory(Option::Universe u, uint16_t type,
+                                          const OptionBuffer& buf) {
+        Option* option = new Option(u, type, buf);
+        return OptionPtr(option);
+    }
 };
 
 static const uint8_t packed[] = {
@@ -41,6 +56,78 @@ static const uint8_t packed[] = {
     1,  1, 0, 1, 114 // opt5 (5 bytes)
 };
 
+TEST(LibDhcpTest, optionFactory) {
+    OptionBuffer buf;
+    // Factory functions for specific options must be registered before
+    // they can be used to create options instances. Otherwise exception
+    // is rised.
+    EXPECT_THROW(LibDHCP::optionFactory(Option::V4, DHO_SUBNET_MASK, buf),
+                 isc::BadValue);
+
+    // Let's register some factory functions (two v4 and one v6 function).
+    // Registration may trigger exception if function for the specified
+    // option has been registered already.
+    ASSERT_NO_THROW(
+        LibDHCP::OptionFactoryRegister(Option::V4, DHO_SUBNET_MASK,
+                                       &LibDhcpTest::genericOptionFactory);
+    );
+    ASSERT_NO_THROW(
+        LibDHCP::OptionFactoryRegister(Option::V4, DHO_TIME_OFFSET,
+                                       &LibDhcpTest::genericOptionFactory);
+    );
+    ASSERT_NO_THROW(
+        LibDHCP::OptionFactoryRegister(Option::V6, D6O_CLIENTID,
+                                       &LibDhcpTest::genericOptionFactory);
+    );
+
+    // Invoke factory functions for all options (check if registration
+    // was successful).
+    OptionPtr opt_subnet_mask;
+    opt_subnet_mask = LibDHCP::optionFactory(Option::V4,
+                                             DHO_SUBNET_MASK,
+                                             buf);
+    // Check if non-NULL DHO_SUBNET_MASK option pointer has been returned.
+    ASSERT_TRUE(opt_subnet_mask);
+    // Validate if type and universe is correct.
+    EXPECT_EQ(Option::V4, opt_subnet_mask->getUniverse());
+    EXPECT_EQ(DHO_SUBNET_MASK, opt_subnet_mask->getType());
+    // Expect that option does not have content..
+    EXPECT_EQ(0, opt_subnet_mask->len() - opt_subnet_mask->getHeaderLen());
+
+    // Fill the time offset buffer with 4 bytes of data. Each byte set to 1.
+    OptionBuffer time_offset_buf(4, 1);
+    OptionPtr opt_time_offset;
+    opt_time_offset = LibDHCP::optionFactory(Option::V4,
+                                             DHO_TIME_OFFSET,
+                                             time_offset_buf);
+    // Check if non-NULL DHO_TIME_OFFSET option pointer has been returned.
+    ASSERT_TRUE(opt_time_offset);
+    // Validate if option length, type and universe is correct.
+    EXPECT_EQ(Option::V4, opt_time_offset->getUniverse());
+    EXPECT_EQ(DHO_TIME_OFFSET, opt_time_offset->getType());
+    EXPECT_EQ(time_offset_buf.size(),
+              opt_time_offset->len() - opt_time_offset->getHeaderLen());
+    // Validate data in the option.
+    EXPECT_TRUE(std::equal(time_offset_buf.begin(), time_offset_buf.end(),
+                           opt_time_offset->getData().begin()));
+
+    // Fill the client id buffer with 20 bytes of data. Each byte set to 2.
+    OptionBuffer clientid_buf(20, 2);
+    OptionPtr opt_clientid;
+    opt_clientid = LibDHCP::optionFactory(Option::V6,
+                                          D6O_CLIENTID,
+                                          clientid_buf);
+    // Check if non-NULL D6O_CLIENTID option pointer has been returned.
+    ASSERT_TRUE(opt_clientid);
+    // Validate if option length, type and universe is correct.
+    EXPECT_EQ(Option::V6, opt_clientid->getUniverse());
+    EXPECT_EQ(D6O_CLIENTID, opt_clientid->getType());
+    EXPECT_EQ(clientid_buf.size(), opt_clientid->len() - opt_clientid->getHeaderLen());
+    // Validate data in the option.
+    EXPECT_TRUE(std::equal(clientid_buf.begin(), clientid_buf.end(),
+                           opt_clientid->getData().begin()));
+}
+
 TEST(LibDhcpTest, packOptions6) {
     OptionBuffer buf(512);
     isc::dhcp::Option::OptionCollection opts; // list of options

+ 17 - 15
tests/tools/perfdhcp/Makefile.am

@@ -1,4 +1,4 @@
-SUBDIRS = . tests
+SUBDIRS = . tests templates
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
@@ -18,25 +18,27 @@ if USE_STATIC_LINK
 AM_LDFLAGS += -static
 endif
 
-lib_LTLIBRARIES = libb10_perfdhcp++.la
-libb10_perfdhcp___la_SOURCES = command_options.cc command_options.h
-libb10_perfdhcp___la_SOURCES += localized_option.h
-libb10_perfdhcp___la_SOURCES += perf_pkt6.cc perf_pkt6.h
-libb10_perfdhcp___la_SOURCES += perf_pkt4.cc perf_pkt4.h
-libb10_perfdhcp___la_SOURCES += pkt_transform.cc pkt_transform.h
-libb10_perfdhcp___la_SOURCES += stats_mgr.h
-
+pkglibexec_PROGRAMS = perfdhcp2
+perfdhcp2_SOURCES = main.cc
+perfdhcp2_SOURCES += command_options.cc command_options.h
+perfdhcp2_SOURCES += localized_option.h
+perfdhcp2_SOURCES += perf_pkt6.cc perf_pkt6.h
+perfdhcp2_SOURCES += perf_pkt4.cc perf_pkt4.h
+perfdhcp2_SOURCES += pkt_transform.cc pkt_transform.h
+perfdhcp2_SOURCES += stats_mgr.h
+perfdhcp2_SOURCES += test_control.cc test_control.h
 libb10_perfdhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
 
+perfdhcp2_CXXFLAGS = $(AM_CXXFLAGS)
 if USE_CLANGPP
 # Disable unused parameter warning caused by some of the
 # Boost headers when compiling with clang.
-libb10_perfdhcp___la_CXXFLAGS += -Wno-unused-parameter
+perfdhcp2_CXXFLAGS += -Wno-unused-parameter
 endif
 
-libb10_perfdhcp___la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
-libb10_perfdhcp___la_LIBADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
-libb10_perfdhcp___la_LIBADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+perfdhcp2_LDADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+perfdhcp2_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+perfdhcp2_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 
-pkglibexec_PROGRAMS  = perfdhcp
-perfdhcp_SOURCES  = perfdhcp.c
+#pkglibexec_PROGRAMS  = perfdhcp
+#perfdhcp_SOURCES  = perfdhcp.c

+ 187 - 11
tests/tools/perfdhcp/command_options.cc

@@ -12,6 +12,7 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <config.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdint.h>
@@ -20,9 +21,11 @@
 #include <boost/algorithm/string.hpp>
 #include <boost/foreach.hpp>
 #include <boost/lexical_cast.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
 
-#include "exceptions/exceptions.h"
-
+#include <exceptions/exceptions.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/iface_mgr.h>
 #include "command_options.h"
 
 using namespace std;
@@ -54,9 +57,10 @@ CommandOptions::reset() {
     rate_ = 0;
     report_delay_ = 0;
     clients_num_ = 0;
-    mac_prefix_.assign(mac, mac + 6);
-    base_.resize(0);
-    num_request_.resize(0);
+    mac_template_.assign(mac, mac + 6);
+    duid_template_.clear();
+    base_.clear();
+    num_request_.clear();
     period_ = 0;
     drop_time_set_ = 0;
     drop_time_.assign(dt, dt + 2);
@@ -81,6 +85,8 @@ CommandOptions::reset() {
     diags_.clear();
     wrapped_.clear();
     server_name_.clear();
+    generateDuidTemplate();
+    commandline_.clear();
 }
 
 void
@@ -127,9 +133,16 @@ CommandOptions::initialize(int argc, char** argv) {
     int offset_arg = 0;         // Temporary variable holding offset arguments
     std::string sarg;           // Temporary variable for string args
 
+    std::ostringstream stream;
+    stream << "perfdhcp";
+
     // In this section we collect argument values from command line
     // they will be tuned and validated elsewhere
     while((opt = getopt(argc, argv, "hv46r:t:R:b:n:p:d:D:l:P:a:L:s:iBc1T:X:O:E:S:I:x:w:")) != -1) {
+        stream << " -" << opt;
+        if (optarg) {
+            stream << " " << optarg;
+        }  
         switch (opt) {
         case 'v':
             version();
@@ -219,6 +232,7 @@ CommandOptions::initialize(int argc, char** argv) {
 
         case 'l':
             localname_ = std::string(optarg);
+            initIsInterface();
             break;
 
         case 'L':
@@ -312,6 +326,8 @@ CommandOptions::initialize(int argc, char** argv) {
         }
     }
 
+    std::cout << "Running: " << stream.str() << std::endl;
+
     // If the IP version was not specified in the
     // command line, assume IPv4.
     if (ipversion_ == 0) {
@@ -351,7 +367,27 @@ CommandOptions::initialize(int argc, char** argv) {
         }
     }
 
-    // TODO handle -l option with IfaceManager when it is created
+    // Handle the local '-l' address/interface
+    if (!localname_.empty()) {
+        if (server_name_.empty()) {
+            if (is_interface_ && (ipversion_ == 4)) {
+                broadcast_ = 1;
+                server_name_ = "255.255.255.255";
+            } else if (is_interface_ && (ipversion_ == 6)) {
+                server_name_ = "FF02::1:2";
+            }
+        }
+    }
+    if (server_name_.empty()) {
+        isc_throw(InvalidParameter,
+                  "without an inteface server is required");
+    }
+
+    // If DUID is not specified from command line we need to
+    // generate one.
+    if (duid_template_.size() == 0) {
+        generateDuidTemplate();
+    }
 }
 
 void
@@ -377,6 +413,17 @@ CommandOptions::initClientsNum() {
 }
 
 void
+CommandOptions::initIsInterface() {
+    is_interface_ = false;
+    if (!localname_.empty()) {
+        dhcp::IfaceMgr& iface_mgr = dhcp::IfaceMgr::instance();
+        if (iface_mgr.getIface(localname_) != NULL)  {
+            is_interface_ = true;
+        }
+    }
+}
+
+void
 CommandOptions::decodeBase(const std::string& base) {
     std::string b(base);
     boost::algorithm::to_lower(b);
@@ -402,7 +449,7 @@ CommandOptions::decodeMac(const std::string& base) {
     // Decode mac address to vector of uint8_t
     std::istringstream s1(base.substr(found + 1));
     std::string token;
-    mac_prefix_.clear();
+    mac_template_.clear();
     // Get pieces of MAC address separated with : (or even ::)
     while (std::getline(s1, token, ':')) {
         unsigned int ui = 0;
@@ -417,16 +464,17 @@ CommandOptions::decodeMac(const std::string& base) {
 
             }
             // If conversion succeeded store byte value
-            mac_prefix_.push_back(ui);
+            mac_template_.push_back(ui);
         }
     }
     // MAC address must consist of 6 octets, otherwise it is invalid
-    check(mac_prefix_.size() != 6, errmsg);
+    check(mac_template_.size() != 6, errmsg);
 }
 
 void
 CommandOptions::decodeDuid(const std::string& base) {
     // Strip argument from duid=
+    std::vector<uint8_t> duid_template;
     size_t found = base.find('=');
     check(found == std::string::npos, "expected -b<base> format for duid is -b duid=<duid>");
     std::string b = base.substr(found + 1);
@@ -446,8 +494,44 @@ CommandOptions::decodeDuid(const std::string& base) {
             isc_throw(isc::InvalidParameter,
                       "invalid characters in DUID provided, exepected hex digits");
         }
-        duid_prefix_.push_back(static_cast<uint8_t>(ui));
+        duid_template.push_back(static_cast<uint8_t>(ui));
     }
+    // @todo Get rid of this limitation when we manage add support
+    // for DUIDs other than LLT. Shorter DUIDs may be useful for
+    // server testing purposes.
+    check(duid_template.size() < 6, "DUID must be at least 6 octets long");
+    // Assign the new duid only if successfully generated.
+    std::swap(duid_template, duid_template_);
+}
+
+void
+CommandOptions::generateDuidTemplate() {
+    using namespace boost::posix_time;
+    // Duid template will be most likely generated only once but
+    // it is ok if it is called more then once so we simply
+    //  regenerate it and discard previous value.
+    duid_template_.clear();
+    const uint8_t duid_template_len = 14;
+    duid_template_.resize(duid_template_len);
+    // The first four octets consist of DUID LLT and hardware type.
+    duid_template_[0] = DUID_LLT >> 8;
+    duid_template_[1] = DUID_LLT & 0xff;
+    duid_template_[2] = HWTYPE_ETHERNET >> 8;
+    duid_template_[3] = HWTYPE_ETHERNET & 0xff;
+    
+    // As described in RFC3315: 'the time value is the time
+    // that the DUID is generated represented in seconds
+    // since midnight (UTC), January 1, 2000, modulo 2^32.'
+    ptime now = microsec_clock::universal_time();
+    ptime duid_epoch(from_iso_string("20000101T000000"));
+    time_period period(duid_epoch, now);
+    uint32_t duration_sec = htonl(period.length().total_seconds());
+    memcpy(&duid_template_[4], &duration_sec, 4);
+
+    // Set link layer address (6 octets). This value may be
+    // randomized before sending a packet to simulate different
+    // clients.
+    memcpy(&duid_template_[8], &mac_template_[0], 6);
 }
 
 uint8_t
@@ -565,6 +649,98 @@ CommandOptions::nonEmptyString(const std::string& errmsg) const {
 }
 
 void
+CommandOptions::printCommandLine() const {
+    std::cout << "IPv" << static_cast<int>(ipversion_) << std::endl;
+    if (exchange_mode_ == DO_SA) {
+        if (ipversion_ == 4) {
+            std::cout << "DISCOVER-OFFER only" << std::endl;
+        } else {
+            std::cout << "SOLICIT-ADVERETISE only" << std::endl;
+        }
+    } 
+    if (rate_ != 0) {
+        std::cout << "rate[1/s]=" << rate_ <<  std::endl;
+    }
+    if (report_delay_ != 0) {
+        std::cout << "report[s]=" << report_delay_ << std::endl;
+    }
+    if (clients_num_ != 0) {
+        std::cout << "clients=" << clients_num_ << std::endl;
+    } 
+    for (int i = 0; i < base_.size(); ++i) {
+        std::cout << "base[" << i << "]=" << base_[i] <<  std::endl;
+    }
+    for (int i = 0; i < num_request_.size(); ++i) {
+        std::cout << "num-request[" << i << "]=" << num_request_[i] << std::endl;
+    }
+    if (period_ != 0) {
+        std::cout << "test-period=" << period_ << std::endl;
+    }
+    for (int i = 0; i < drop_time_.size(); ++i) {
+        std::cout << "drop-time[" << i << "]=" << drop_time_[i] << std::endl;
+    }
+    for (int i = 0; i < max_drop_.size(); ++i) {
+        std::cout << "max-drop{" << i << "]=" << max_drop_[i] << std::endl;
+    }
+    for (int i = 0; i < max_pdrop_.size(); ++i) {
+        std::cout << "max-pdrop{" << i << "]=" << max_pdrop_[i] << std::endl;
+    }
+    if (preload_ != 0) {
+        std::cout << "preload=" << preload_ <<  std::endl;
+    }
+    std::cout << "aggressivity=" << aggressivity_ << std::endl;
+    if (getLocalPort() != 0) {
+        std::cout << "local-port=" << local_port_ <<  std::endl;
+    }
+    if (seeded_) {
+        std::cout << "seed=" << seed_ << std::endl;
+    }
+    if (broadcast_) {
+        std::cout << "broadcast" << std::endl;
+    }
+    if (rapid_commit_) {
+        std::cout << "rapid-commit" << std::endl;
+    }
+    if (use_first_) {
+        std::cout << "use-first" << std::endl;
+    }
+    for (int i = 0; i < template_file_.size(); ++i) {
+        std::cout << "template-file[" << i << "]=" << template_file_[i] << std::endl;
+    }
+    for (int i = 0; i < xid_offset_.size(); ++i) {
+        std::cout << "xid-offset[" << i << "]=" << xid_offset_[i] << std::endl;
+    }
+    if (elp_offset_ != 0) {
+        std::cout << "elp-offset=" << elp_offset_ << std::endl;
+    }
+    for (int i = 0; i < rnd_offset_.size(); ++i) {
+        std::cout << "rnd-offset[" << i << "]=" << rnd_offset_[i] << std::endl;
+    }
+    if (sid_offset_ != 0) {
+        std::cout << "sid-offset=" << sid_offset_ << std::endl;
+    }
+    if (rip_offset_ != 0) {
+        std::cout << "rip-offset=" << rip_offset_ << std::endl;
+    }
+    if (!diags_.empty()) {
+        std::cout << "diagnostic-selectors=" << diags_ <<  std::endl;
+    }
+    if (!wrapped_.empty()) {
+        std::cout << "wrapped=" << wrapped_ << std::endl;
+    }
+    if (!localname_.empty()) {
+        if (is_interface_) {
+            std::cout << "interface=" << localname_ << std::endl;
+        } else {
+            std::cout << "local-addr=" << localname_ << std::endl;
+        }
+    }
+    if (!server_name_.empty()) {
+        std::cout << "server=" << server_name_ << std::endl;
+    }
+}
+
+void
 CommandOptions::usage() const {
 	fprintf(stdout, "%s",
 "perfdhcp [-hv] [-4|-6] [-r<rate>] [-t<report>] [-R<range>] [-b<base>]\n"
@@ -691,7 +867,7 @@ CommandOptions::usage() const {
 
 void
 CommandOptions::version() const {
-	fprintf(stdout, "version 0.01\n");
+    std::cout << "VERSION: " << VERSION << std::endl;
 }
 
 

+ 133 - 114
tests/tools/perfdhcp/command_options.h

@@ -23,7 +23,7 @@
 namespace isc {
 namespace perfdhcp {
 
-/// \brief Command Options
+/// \brief Command Options.
 ///
 /// This class is responsible for parsing the command-line and storing the
 /// specified options.
@@ -49,64 +49,64 @@ public:
     /// command line options.
     void reset();
 
-    /// \brief Parse command line
+    /// \brief Parse command line.
     ///
     /// Parses the command line and stores the selected options
     /// in class data members.
     ///
     /// \param argc Argument count passed to main().
     /// \param argv Argument value array passed to main().
-    /// \throws isc::InvalidParameter if parse fails
+    /// \throws isc::InvalidParameter if parse fails.
     void parse(int argc, char** const argv);
 
-    /// \brief Returns IP version
+    /// \brief Returns IP version.
     ///
-    /// \return IP version to be used
+    /// \return IP version to be used.
     uint8_t getIpVersion() const { return ipversion_; }
 
-    /// \brief Returns packet exchange mode
+    /// \brief Returns packet exchange mode.
     ///
-    /// \return packet exchange mode
+    /// \return packet exchange mode.
     ExchangeMode getExchangeMode() const { return exchange_mode_; }
 
-    /// \brief Returns echange rate
+    /// \brief Returns echange rate.
     ///
-    /// \return exchange rate per second
+    /// \return exchange rate per second.
     int getRate() const { return rate_; }
 
-    /// \brief Returns delay between two performance reports
+    /// \brief Returns delay between two performance reports.
     ///
-    /// \return delay between two consecutive performance reports
+    /// \return delay between two consecutive performance reports.
     int getReportDelay() const { return report_delay_; }
 
-    /// \brief Returns number of simulated clients
+    /// \brief Returns number of simulated clients.
     ///
-    /// \return number of simulated clients
+    /// \return number of simulated clients.
     uint32_t getClientsNum() const { return clients_num_; }
 
-    /// \brief Returns MAC address prefix
+    /// \brief Returns MAC address template.
     ///
-    /// \ return MAC address prefix to simulate different clients
-    std::vector<uint8_t> getMacPrefix() const { return mac_prefix_; }
+    /// \return MAC address template to simulate different clients.
+    std::vector<uint8_t> getMacTemplate() const { return mac_template_; }
 
-    /// \brief Returns DUID prefix
+    /// \brief Returns DUID template.
     ///
-    /// \return DUID prefix to simulate different clients
-    std::vector<uint8_t> getDuidPrefix() const { return duid_prefix_; }
+    /// \return DUID template to simulate different clients.
+    std::vector<uint8_t> getDuidTemplate() const { return duid_template_; }
 
-    /// \brief Returns base values
+    /// \brief Returns base values.
     ///
-    /// \return all base values specified
+    /// \return all base values specified.
     std::vector<std::string> getBase() const { return base_; }
 
-    /// \brief Returns maximum number of exchanges
+    /// \brief Returns maximum number of exchanges.
     ///
-    /// \return number of exchange requests before test is aborted
+    /// \return number of exchange requests before test is aborted.
     std::vector<int> getNumRequests() const { return num_request_; }
 
-    /// \brief Returns test period
+    /// \brief Returns test period.
     ///
-    /// \return test period before it is aborted
+    /// \return test period before it is aborted.
     int getPeriod() const { return period_; }
 
     /// \brief Returns drop time
@@ -114,136 +114,139 @@ public:
     /// The method returns maximum time elapsed from
     /// sending the packet before it is assumed dropped.
     ///
-    /// \return return time before request is assumed dropped
+    /// \return return time before request is assumed dropped.
     std::vector<double> getDropTime() const { return drop_time_; }
 
-    /// \brief Returns maximum drops number
+    /// \brief Returns maximum drops number.
     ///
     /// Returns maximum number of packet drops before
     /// aborting a test.
     ///
-    /// \return maximum number of dropped requests
+    /// \return maximum number of dropped requests.
     std::vector<int> getMaxDrop() const { return max_drop_; }
 
-    /// \brief Returns maximal percentage of drops
+    /// \brief Returns maximal percentage of drops.
     ///
     /// Returns maximal percentage of packet drops
     /// before aborting a test.
     ///
-    /// \return maximum percentage of lost requests
+    /// \return maximum percentage of lost requests.
     std::vector<double> getMaxDropPercentage() const { return max_pdrop_; }
 
-    /// \brief Returns local address or interface name
+    /// \brief Returns local address or interface name.
     ///
-    /// \return local address or interface name
+    /// \return local address or interface name.
     std::string getLocalName() const { return localname_; }
 
-    /// \brief Checks if interface name was used
+    /// \brief Checks if interface name was used.
     ///
     /// The method checks if interface name was used
     /// rather than address.
     ///
-    /// \return true if interface name was used
+    /// \return true if interface name was used.
     bool isInterface() const { return is_interface_; }
 
-    /// \brief Returns number of preload exchanges
+    /// \brief Returns number of preload exchanges.
     ///
-    /// \return number of preload exchanges
+    /// \return number of preload exchanges.
     int getPreload() const { return preload_; }
 
-    /// \brief Returns aggressivity value
+    /// \brief Returns aggressivity value.
     ///
-    /// \return aggressivity value
+    /// \return aggressivity value.
     int getAggressivity() const { return aggressivity_; }
 
-    /// \brief Returns local port number
+    /// \brief Returns local port number.
     ///
-    /// \return local port number
+    /// \return local port number.
     int getLocalPort() const { return local_port_; }
 
-    /// \brief Checks if seed provided
+    /// \brief Checks if seed provided.
     ///
-    /// \return true if seed was provided
+    /// \return true if seed was provided.
     bool isSeeded() const { return seeded_; }
 
-    /// \brief Returns radom seed
+    /// \brief Returns radom seed.
     ///
-    /// \return random seed
+    /// \return random seed.
     uint32_t getSeed() const { return seed_; }
 
-    /// \brief Checks if broadcast address is to be used
+    /// \brief Checks if broadcast address is to be used.
     ///
-    /// \return true if broadcast address is to be used
+    /// \return true if broadcast address is to be used.
     bool isBroadcast() const { return broadcast_; }
 
-    /// \brief Check if rapid commit option used
+    /// \brief Check if rapid commit option used.
     ///
-    /// \return true if rapid commit option is used
+    /// \return true if rapid commit option is used.
     bool isRapidCommit() const { return rapid_commit_; }
 
-    /// \brief Check if server-ID to be taken from first package
+    /// \brief Check if server-ID to be taken from first package.
     ///
-    /// \return true if server-iD to be taken from first package
+    /// \return true if server-iD to be taken from first package.
     bool isUseFirst() const { return use_first_; }
 
-    /// \brief Returns template file names
+    /// \brief Returns template file names.
     ///
-    /// \return template file names
+    /// \return template file names.
     std::vector<std::string> getTemplateFiles() const { return template_file_; }
 
-    /// brief Returns template offsets for xid
+    /// brief Returns template offsets for xid.
     ///
-    /// \return template offsets for xid
+    /// \return template offsets for xid.
     std::vector<int> getTransactionIdOffset() const { return xid_offset_; }
 
-    /// \brief Returns template offsets for rnd
+    /// \brief Returns template offsets for rnd.
     ///
-    /// \return template offsets for rnd
+    /// \return template offsets for rnd.
     std::vector<int> getRandomOffset() const { return rnd_offset_; }
 
-    /// \brief Returns template offset for elapsed time
+    /// \brief Returns template offset for elapsed time.
     ///
-    /// \return template offset for elapsed time
+    /// \return template offset for elapsed time.
     int getElapsedTimeOffset() const { return elp_offset_; }
 
-    /// \brief Returns template offset for server-ID
+    /// \brief Returns template offset for server-ID.
     ///
-    /// \return template offset for server-ID
+    /// \return template offset for server-ID.
     int getServerIdOffset() const { return sid_offset_; }
 
-    /// \brief Returns template offset for requested IP
+    /// \brief Returns template offset for requested IP.
     ///
-    /// \return template offset for requested IP
+    /// \return template offset for requested IP.
     int getRequestedIpOffset() const { return rip_offset_; }
 
-    /// \brief Returns diagnostic selectors
+    /// \brief Returns diagnostic selectors.
     ///
-    /// \return diagnostics selector
+    /// \return diagnostics selector.
     std::string getDiags() const { return diags_; }
 
-    /// \brief Returns wrapped command
+    /// \brief Returns wrapped command.
     ///
-    /// \return wrapped command (start/stop)
+    /// \return wrapped command (start/stop).
     std::string getWrapped() const { return wrapped_; }
 
-    /// \brief Returns server name
+    /// \brief Returns server name.
     ///
-    /// \return server name
+    /// \return server name.
     std::string getServerName() const { return server_name_; }
+    
+    /// \brief Print command line arguments.
+    void printCommandLine() const;
 
-    /// \brief Print usage
+    /// \brief Print usage.
     ///
-    /// Prints perfdhcp usage
+    /// Prints perfdhcp usage.
     void usage() const;
 
-    /// \brief Print program version
+    /// \brief Print program version.
     ///
-    /// Prints perfdhcp version
+    /// Prints perfdhcp version.
     void version() const;
 
 private:
 
-    /// \brief Default Constructor
+    /// \brief Default Constructor.
     ///
     /// Private constructor as this is a singleton class.
     /// Use CommandOptions::instance() to get instance of it.
@@ -251,57 +254,64 @@ private:
         reset();
     }
 
-    /// \brief Initializes class members based command line
+    /// \brief Initializes class members based on the command line.
     ///
-    /// Reads each command line parameter and sets class member values
+    /// Reads each command line parameter and sets class member values.
     ///
     /// \param argc Argument count passed to main().
     /// \param argv Argument value array passed to main().
-    /// \throws isc::InvalidParameter if command line options initialization fails
+    /// \throws isc::InvalidParameter if command line options initialization fails.
     void initialize(int argc, char** argv);
 
-    /// \brief Validates initialized options
+    /// \brief Validates initialized options.
     ///
-    /// \throws isc::InvalidParameter if command line validation fails
+    /// \throws isc::InvalidParameter if command line validation fails.
     void validate() const;
 
-    /// \brief Throws !InvalidParameter exception if condition is true
+    /// \brief Throws !InvalidParameter exception if condition is true.
     ///
     /// Convenience function that throws an InvalidParameter exception if
-    /// the condition argument is true
+    /// the condition argument is true.
     ///
-    /// \param condition Condition to be checked
-    /// \param errmsg Error message in exception
-    /// \throws isc::InvalidParameter if condition argument true
+    /// \param condition Condition to be checked.
+    /// \param errmsg Error message in exception.
+    /// \throws isc::InvalidParameter if condition argument true.
     inline void check(bool condition, const std::string& errmsg) const;
 
-    /// \brief Casts command line argument to positive integer
+    /// \brief Casts command line argument to positive integer.
     ///
-    /// \param errmsg Error message if lexical cast fails
-    /// \throw InvalidParameter if lexical cast fails
+    /// \param errmsg Error message if lexical cast fails.
+    /// \throw InvalidParameter if lexical cast fails.
     int positiveInteger(const std::string& errmsg) const;
 
-    /// \brief Casts command line argument to non-negative integer
+    /// \brief Casts command line argument to non-negative integer.
     ///
-    /// \param errmsg Error message if lexical cast fails
-    /// \throw InvalidParameter if lexical cast fails
+    /// \param errmsg Error message if lexical cast fails.
+    /// \throw InvalidParameter if lexical cast fails.
     int nonNegativeInteger(const std::string& errmsg) const;
 
-    /// \brief Returns command line string if it is not empty
+    /// \brief Returns command line string if it is not empty.
     ///
-    /// \param errmsg Error message if string is empty
-    /// \throw InvalidParameter if string is empty
+    /// \param errmsg Error message if string is empty.
+    /// \throw InvalidParameter if string is empty.
     std::string nonEmptyString(const std::string& errmsg) const;
 
-    /// \brief Set number of clients
+    /// \brief Set number of clients.
     ///
     /// Interprets the getopt() "opt" global variable as the number of clients
     /// (a non-negative number).  This value is specified by the "-R" switch.
     ///
-    /// \throw InvalidParameter if -R<value> is wrong
+    /// \throw InvalidParameter if -R<value> is wrong.
     void initClientsNum();
 
-    /// \brief Decodes base provided with -b<base>
+    /// \brief Sets value indicating if interface name was given.
+    ///
+    /// Method checks if the command line argument given with
+    /// '-l' option is the interface name. The is_interface_ member
+    /// is set accordingly.
+    void initIsInterface();
+
+    /// \brief Decodes base provided with -b<base>.
     ///
     /// Function decodes argument of -b switch, which
     /// specifies a base value used to generate unique
@@ -311,39 +321,47 @@ private:
     /// - -b mac=00:01:02:03:04:05
     /// - -b duid=0F1234 (duid can be up to 128 hex digits)
     //  Function will decode 00:01:02:03:04:05 and/or
-    /// 0F1234 respectively and initialize mac_prefix_
-    /// and/or duid_prefix_ members
+    /// 0F1234 respectively and initialize mac_template_
+    /// and/or duid_template_ members.
     ///
-    /// \param base Base in string format
-    /// \throws isc::InvalidParameter if base is invalid
+    /// \param base Base in string format.
+    /// \throws isc::InvalidParameter if base is invalid.
     void decodeBase(const std::string& base);
 
-    /// \brief Decodes base MAC address provided with -b<base>
+    /// \brief Decodes base MAC address provided with -b<base>.
     ///
     /// Function decodes parameter given as -b mac=00:01:02:03:04:05
-    /// The function will decode 00:01:02:03:04:05 initialize mac_prefix_
+    /// The function will decode 00:01:02:03:04:05 initialize mac_template_
     /// class member.
-    /// Provided MAC address is for example only
+    /// Provided MAC address is for example only.
     ///
-    /// \param base Base string given as -b mac=00:01:02:03:04:05
-    /// \throws isc::InvalidParameter if mac address is invalid
+    /// \param base Base string given as -b mac=00:01:02:03:04:05.
+    /// \throws isc::InvalidParameter if mac address is invalid.
     void decodeMac(const std::string& base);
 
-    /// \brief Decodes base DUID provided with -b<base>
+    /// \brief Decodes base DUID provided with -b<base>.
     ///
-    /// Function decodes parameter given as -b duid=0F1234
-    /// The function will decode 0F1234 and initialize duid_prefix_
+    /// Function decodes parameter given as -b duid=0F1234.
+    /// The function will decode 0F1234 and initialize duid_template_
     /// class member.
     /// Provided DUID is for example only.
     ///
-    /// \param base Base string given as -b duid=0F1234
-    /// \throws isc::InvalidParameter if DUID is invalid
+    /// \param base Base string given as -b duid=0F1234.
+    /// \throws isc::InvalidParameter if DUID is invalid.
     void decodeDuid(const std::string& base);
+    
+    /// \brief Generates DUID-LLT (based on link layer address).
+    ///
+    /// Function generates DUID based on link layer address and
+    /// initiates duid_template_ value with it.
+    /// \todo add support to generate DUIDs other than based on
+    /// 6-octets long MACs (e.g. DUID-UUID.
+    void generateDuidTemplate();
 
-    /// \brief Converts two-digit hexadecimal string to a byte
+    /// \brief Converts two-digit hexadecimal string to a byte.
     ///
-    /// \param hex_text Hexadecimal string e.g. AF
-    /// \throw isc::InvalidParameter if string does not represent hex byte
+    /// \param hex_text Hexadecimal string e.g. AF.
+    /// \throw isc::InvalidParameter if string does not represent hex byte.
     uint8_t convertHexString(const std::string& hex_text) const;
 
     uint8_t ipversion_;                      ///< IP protocol version to be used, expected values are:
@@ -353,9 +371,9 @@ private:
     int report_delay_;                       ///< Delay between generation of two consecutive
                                              ///< performance reports
     uint32_t clients_num_;                   ///< Number of simulated clients (aka randomization range).
-    std::vector<uint8_t> mac_prefix_;        ///< MAC address prefix used to generate unique DUIDs
+    std::vector<uint8_t> mac_template_;      ///< MAC address template used to generate unique DUIDs
                                              ///< for simulated clients.
-    std::vector<uint8_t> duid_prefix_;       ///< DUID prefix used to generate unique DUIDs for
+    std::vector<uint8_t> duid_template_;     ///< DUID template used to generate unique DUIDs for
                                              ///< simulated clients
     std::vector<std::string> base_;          ///< Collection of base values specified with -b<value>
                                              ///< options. Supported "bases" are mac=<mac> and duid=<duid>
@@ -404,6 +422,7 @@ private:
     std::string wrapped_;                    ///< Wrapped command specified as -w<value>. Expected
                                              ///< values are start and stop.
     std::string server_name_;                ///< Server name specified as last argument of command line.
+    std::string commandline_;                ///< Entire command line as typed in by the user.
 };
 
 } // namespace perfdhcp

+ 68 - 41
tests/tools/perfdhcp/localized_option.h

@@ -16,6 +16,8 @@
 #define __LOCALIZED_OPTION_H
 
 #include <dhcp/pkt6.h>
+#include <dhcp/option6_ia.h>
+#include <util/buffer.h>
 
 namespace isc {
 namespace perfdhcp {
@@ -42,69 +44,86 @@ namespace perfdhcp {
 ///
 class LocalizedOption : public dhcp::Option {
 public:
-    /// \brief Constructor, sets default (0) option offset
-    ///
-    /// \param u specifies universe (V4 or V6)
-    /// \param type option type (0-255 for V4 and 0-65535 for V6)
-    /// \param data content of the option
-    LocalizedOption(dhcp::Option::Universe u,
-                    uint16_t type,
-                    const dhcp::OptionBuffer& data) :
-        dhcp::Option(u, type, data),
-        offset_(0) {
-    }
 
-
-    /// \brief Constructor, used to create localized option from buffer
+    /// \brief Constructor, used to create localized option from buffer.
     ///
-    /// \param u specifies universe (V4 or V6)
-    /// \param type option type (0-255 for V4 and 0-65535 for V6)
-    /// \param data content of the option
-    /// \param offset location of option in a packet (zero is default)
+    /// This constructor creates localized option using whole provided
+    /// option buffer.
+    ///
+    /// \param u universe (V4 or V6).
+    /// \param type option type (0-255 for V4 and 0-65535 for V6).
+    /// Option values 0 and 255 (v4) and 0 (v6) are not valid option
+    /// codes but they are accepted here for the server testing purposes.
+    /// \param data content of the option.
+    /// \param offset location of option in a packet (zero is default).
     LocalizedOption(dhcp::Option::Universe u,
                     uint16_t type,
                     const dhcp::OptionBuffer& data,
-                    const size_t offset) :
+                    const size_t offset = 0) :
         dhcp::Option(u, type, data),
-        offset_(offset) {
+        offset_(offset), option_valid_(true) {
     }
 
-    /// \brief Constructor, sets default (0) option offset
+    /// \brief Constructor, used to create option from buffer iterators.
     ///
-    /// This contructor is similar to the previous one, but it does not take
-    /// the whole vector<uint8_t>, but rather subset of it.
+    /// This constructor creates localized option using part of the
+    /// option buffer pointed by iterators.
     ///
     /// \param u specifies universe (V4 or V6)
     /// \param type option type (0-255 for V4 and 0-65535 for V6)
     /// \param first iterator to the first element that should be copied
     /// \param last iterator to the next element after the last one
     ///        to be copied.
+    /// \param offset offset of option in a packet (zero is default)
     LocalizedOption(dhcp::Option::Universe u,
                     uint16_t type,
                     dhcp::OptionBufferConstIter first,
-                    dhcp::OptionBufferConstIter last) :
+                    dhcp::OptionBufferConstIter last,
+                    const size_t offset = 0) :
         dhcp::Option(u, type, first, last),
-        offset_(0) {
+        offset_(offset), option_valid_(true) {
     }
 
-
-    /// \brief Constructor, used to create option from buffer iterators
+    /// \brief Copy constructor, creates LocalizedOption from Option6IA.
     ///
-    /// This contructor is similar to the previous one, but it does not take
-    /// the whole vector<uint8_t>, but rather subset of it.
+    /// This copy constructor creates regular option from Option6IA.
+    /// The data from Option6IA data members are copied to
+    /// option buffer in appropriate sequence.
     ///
-    /// \param u specifies universe (V4 or V6)
-    /// \param type option type (0-255 for V4 and 0-65535 for V6)
-    /// \param first iterator to the first element that should be copied
-    /// \param last iterator to the next element after the last one
-    ///        to be copied.
-    /// \param offset offset of option in a packet (zero is default)
-    LocalizedOption(dhcp::Option::Universe u,
-                    uint16_t type,
-                    dhcp::OptionBufferConstIter first,
-                    dhcp::OptionBufferConstIter last, const size_t offset) :
-        dhcp::Option(u, type, first, last),
-        offset_(offset) {
+    /// \param opt_ia option to be copied.
+    /// \param offset location of the option in a packet.
+    LocalizedOption(const boost::shared_ptr<dhcp::Option6IA>& opt_ia,
+                    const size_t offset) :
+        dhcp::Option(Option::V6, 0, dhcp::OptionBuffer()),
+        offset_(offset), option_valid_(false) {
+        // If given option is NULL we will mark this new option
+        // as invalid. User may query if option is valid when
+        // object is created.
+        if (opt_ia) {
+            // Set universe and type.
+            universe_ = opt_ia->getUniverse();
+            type_ = opt_ia->getType();
+            util::OutputBuffer buf(opt_ia->len() - opt_ia->getHeaderLen());
+            try {
+                // Try to pack option data into the temporary buffer.
+                opt_ia->pack(buf);
+                if (buf.getLength() > 0) {
+                    const char* buf_data = static_cast<const char*>(buf.getData());
+                    // Option has been packed along with option type flag
+                    // and transaction id so we have to skip first 4 bytes
+                    // when copying temporary buffer option buffer.
+                    data_.assign(buf_data + 4, buf_data + buf.getLength());
+                }
+                option_valid_ = true;
+            } catch (const Exception&) {
+                // If there was an exception somewhere when packing
+                // the data into the buffer we assume that option is
+                // not valid and should not be used.
+                option_valid_ = false;
+            }
+        } else {
+            option_valid_ = false;
+        }
     }
 
     /// \brief Returns offset of an option in a DHCP packet.
@@ -112,12 +131,20 @@ public:
     /// \return option offset in a packet
     size_t getOffset() const { return offset_; };
 
+    /// \brief Checks if option is valid.
+    ///
+    /// \return true, if option is valid.
+    virtual bool valid() {
+        return (Option::valid() && option_valid_);
+    }
+
 private:
     size_t offset_;   ///< Offset of DHCP option in a packet
+    bool option_valid_; ///< Is option valid.
 };
 
 
-} // namespace perfdhcp
+} // namespace isc::perfdhcp
 } // namespace isc
 
 #endif // __LOCALIZED_OPTION_H

+ 50 - 0
tests/tools/perfdhcp/main.cc

@@ -0,0 +1,50 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <iostream>
+#include <stdint.h>
+
+#include <config.h>
+#include <exceptions/exceptions.h>
+
+#include "test_control.h"
+#include "command_options.h"
+
+using namespace isc::perfdhcp;
+
+int
+main(int argc, char* argv[]) {
+    CommandOptions& command_options = CommandOptions::instance();
+    try {
+        command_options.parse(argc, argv);
+    } catch(isc::Exception& e) {
+        std::cout << "Error parsing command line options: "
+                  << e.what() << std::endl;
+        command_options.usage();
+        return(1);
+    }
+    try{
+        TestControl& test_control = TestControl::instance();
+        test_control.run();
+    } catch (isc::Exception& e) {
+        std::cout << "Error running perfdhcp: " << e.what() << std::endl;
+        std::string diags(command_options.getDiags());
+        if (diags.find('e') != std::string::npos) {
+            std::cout << "Fatal error" << std::endl;
+        }
+        return(1);
+    }
+    return(0);
+}
+

+ 9 - 1
tests/tools/perfdhcp/perf_pkt4.cc

@@ -16,7 +16,6 @@
 #include <dhcp/dhcp6.h>
 
 #include "perf_pkt4.h"
-#include "pkt_transform.h"
 
 using namespace std;
 using namespace isc;
@@ -58,5 +57,14 @@ PerfPkt4::rawUnpack() {
     return (res);
 }
 
+void
+PerfPkt4::writeAt(size_t dest_pos,
+                  std::vector<uint8_t>::iterator first,
+                  std::vector<uint8_t>::iterator last) {
+    return (PktTransform::writeAt(data_, dest_pos, first, last));
+}
+
+
+
 } // namespace perfdhcp
 } // namespace isc

+ 26 - 0
tests/tools/perfdhcp/perf_pkt4.h

@@ -20,6 +20,7 @@
 #include <dhcp/pkt4.h>
 
 #include "localized_option.h"
+#include "pkt_transform.h"
 
 namespace isc {
 namespace perfdhcp {
@@ -102,11 +103,36 @@ public:
     /// \return false If unpack operation failed.
     bool rawUnpack();
 
+    /// \brief Replace contents of buffer with data.
+    ///
+    /// Function replaces part of the buffer with data from vector.
+    ///
+    /// \param dest_pos position in buffer where data is replaced.
+    /// \param first beginning of data range in source vector.
+    /// \param last end of data range in source vector.
+    void writeAt(size_t dest_pos,
+                 std::vector<uint8_t>::iterator first,
+                 std::vector<uint8_t>::iterator last);
+    
+    /// \brief Replace contents of buffer with value.
+    ///
+    /// Function replaces part of buffer with value.
+    ///
+    /// \param dest_pos position in buffer where value is
+    /// to be written.
+    /// \param val value to be written.
+    template<typename T>
+    void writeValueAt(size_t dest_pos, T val) {
+        PktTransform::writeValueAt<T>(data_, dest_pos, val);
+    }
+
 private:
     size_t transid_offset_;      ///< transaction id offset
 
 };
 
+typedef boost::shared_ptr<PerfPkt4> PerfPkt4Ptr;
+
 } // namespace perfdhcp
 } // namespace isc
 

+ 8 - 0
tests/tools/perfdhcp/perf_pkt6.cc

@@ -60,5 +60,13 @@ PerfPkt6::rawUnpack() {
     return (res);
 }
 
+void
+PerfPkt6::writeAt(size_t dest_pos,
+                     std::vector<uint8_t>::iterator first,
+                     std::vector<uint8_t>::iterator last) {
+    return (PktTransform::writeAt(data_, dest_pos, first, last));
+}
+
+
 } // namespace perfdhcp
 } // namespace isc

+ 26 - 0
tests/tools/perfdhcp/perf_pkt6.h

@@ -20,6 +20,7 @@
 #include <dhcp/pkt6.h>
 
 #include "localized_option.h"
+#include "pkt_transform.h"
 
 namespace isc {
 namespace perfdhcp {
@@ -102,11 +103,36 @@ public:
     /// \return false if unpack operation failed.
     bool rawUnpack();
 
+    /// \brief Replace contents of buffer with data.
+    ///
+    /// Function replaces part of the buffer with data from vector.
+    ///
+    /// \param dest_pos position in buffer where data is replaced.
+    /// \param first beginning of data range in source vector.
+    /// \param last end of data range in source vector.
+    void writeAt(size_t dest_pos,
+                 std::vector<uint8_t>::iterator first,
+                 std::vector<uint8_t>::iterator last);
+
+    /// \brief Replace contents of buffer with value.
+    ///
+    /// Function replaces part of buffer with value.
+    ///
+    /// \param dest_pos position in buffer where value is
+    /// to be written.
+    /// \param val value to be written.
+    template<typename T>
+    void writeValueAt(size_t dest_pos, T val) {
+        PktTransform::writeValueAt<T>(data_, dest_pos, val);
+    }
+
 private:
     size_t transid_offset_;      ///< transaction id offset
 
 };
 
+typedef boost::shared_ptr<PerfPkt6> PerfPkt6Ptr;
+
 } // namespace perfdhcp
 } // namespace isc
 

+ 7 - 1
tests/tools/perfdhcp/pkt_transform.cc

@@ -216,7 +216,13 @@ PktTransform::unpackOptions(const OptionBuffer& in_buffer,
                         in_buffer.begin() + offset + opt_len);
     }
 }
-
+    
+void
+PktTransform::writeAt(dhcp::OptionBuffer& in_buffer, size_t dest_pos,
+                      dhcp::OptionBuffer::iterator first,
+                      dhcp::OptionBuffer::iterator last) {
+    memcpy(&in_buffer[dest_pos], &(*first), std::distance(first, last));
+}
 
 } // namespace perfdhcp
 } // namespace isc

+ 30 - 0
tests/tools/perfdhcp/pkt_transform.h

@@ -92,6 +92,35 @@ public:
                        const size_t transid_offset,
                        uint32_t& transid);
 
+    /// \brief Replace contents of buffer with vector.
+    ///
+    /// Function replaces data of the buffer with data from vector.
+    ///
+    /// \param in_buffer destination buffer.
+    /// \param dest_pos position in destination buffer.
+    /// \param first beginning of data range in source vector.
+    /// \param last end of data range in source vector.
+    static void writeAt(dhcp::OptionBuffer& in_buffer, size_t dest_pos,
+                        std::vector<uint8_t>::iterator first,
+                        std::vector<uint8_t>::iterator last);
+
+    /// \brief Replace contents of one vector with uint16 value.
+    ///
+    /// Function replaces data inside one vector with uint16_t value.
+    ///
+    /// \param in_buffer destination buffer.
+    /// \param dest_pos position in destination buffer.
+    /// \param val value to be written.
+    template<typename T>
+    static void writeValueAt(dhcp::OptionBuffer& in_buffer, size_t dest_pos,
+                        T val) {
+        // @todo consider replacing the loop with switch statement
+        // checking sizeof(T).
+        for (int i = 0; i < sizeof(T); ++i) {
+            in_buffer[dest_pos + i] = (val >> 8 * (sizeof(T) - i - 1)) & 0xFF;
+        }
+    }
+
 private:
     /// \brief Replaces contents of options in a buffer.
     ///
@@ -131,6 +160,7 @@ private:
     /// \throw isc::Unexpected if options unpack failed.
     static void unpackOptions(const dhcp::OptionBuffer& in_buffer,
                               const dhcp::Option::OptionCollection& options);
+
 };
 
 } // namespace perfdhcp

+ 119 - 35
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,21 +214,33 @@ public:
         /// }
         /// \endcode
         typedef boost::multi_index_container<
-            boost::shared_ptr<const T>,
+            // Container holds shared_ptr<Pkt4> or shared_ptr<Pkt6> objects.
+            boost::shared_ptr<T>,
+            // List container indexes.
             boost::multi_index::indexed_by<
+                // Sequenced index provides the way to use this container
+                // in the same way as std::list.
                 boost::multi_index::sequenced<>,
+                // The other index keeps products of transaction id.
                 boost::multi_index::hashed_non_unique<
-                        boost::multi_index::global_fun<
-                            const boost::shared_ptr<const T>&,
-                            uint32_t,
-                            &ExchangeStats::hashTransid
-                        >
+                    // Specify hash function to get the product of
+                    // transaction id. This product is obtained by calling
+                    // hashTransid() function.
+                    boost::multi_index::global_fun<
+                        // Hashing function takes shared_ptr<Pkt4> or
+                        // shared_ptr<Pkt6> as argument.
+                        const boost::shared_ptr<T>&,
+                        // ... and returns uint32 value.
+                        uint32_t,
+                        // ... and here is a reference to it.
+                        &ExchangeStats::hashTransid
+                    >
                 >
             >
         > 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;
@@ -243,20 +255,21 @@ public:
         /// In this mode all packets are stored throughout the test execution.
         ExchangeStats(const ExchangeType xchg_type, const bool archive_enabled)
             : xchg_type_(xchg_type),
+            sent_packets_(),
+            rcvd_packets_(),
+            archived_packets_(),
+            archive_enabled_(archive_enabled),
             min_delay_(std::numeric_limits<double>::max()),
             max_delay_(0.),
             sum_delay_(0.),
-            orphans_(0),
             sum_delay_squared_(0.),
-            ordered_lookups_(0),
+            orphans_(0),
             unordered_lookup_size_sum_(0),
             unordered_lookups_(0),
+            ordered_lookups_(0),
             sent_packets_num_(0),
-            rcvd_packets_num_(0),
-            sent_packets_(),
-            rcvd_packets_(),
-            archived_packets_(),
-            archive_enabled_(archive_enabled) {
+            rcvd_packets_num_(0)
+        {
             next_sent_ = sent_packets_.begin();
         }
 
@@ -266,7 +279,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");
             }
@@ -280,7 +293,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");
             }
@@ -296,8 +309,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");
             }
@@ -355,7 +368,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");
             }
@@ -366,7 +380,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
@@ -425,13 +439,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.
@@ -548,6 +562,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.
@@ -555,10 +582,9 @@ public:
         /// number of dropped packets and number of orphans.
         void printMainStats() const {
             using namespace std;
-            uint64_t drops = getRcvdPacketsNum() - getSentPacketsNum();
             cout << "sent packets: " << getSentPacketsNum() << endl
                  << "received packets: " << getRcvdPacketsNum() << endl
-                 << "drops: " << drops << endl
+                 << "drops: " << getDroppedPacketsNum() << endl
                  << "orphans: " << getOrphans() << endl;
         }
 
@@ -610,7 +636,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,
@@ -621,7 +647,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();
@@ -761,7 +787,8 @@ public:
     StatsMgr(const bool archive_enabled = false) :
         exchanges_(),
         custom_counters_(),
-        archive_enabled_(archive_enabled) {
+        archive_enabled_(archive_enabled),
+        boot_time_(boost::posix_time::microsec_clock::universal_time()) {
     }
 
     /// \brief Specify new exchange type.
@@ -819,7 +846,7 @@ public:
     ///
     /// \param counter_key key poitinh to the counter in the counters map.
     /// \return pointer to specified counter after incrementation.
-    const CustomCounter& IncrementCounter(const std::string& counter_key) {
+    const CustomCounter& incrementCounter(const std::string& counter_key) {
         CustomCounterPtr counter = getCounter(counter_key);
         return(++(*counter));
     }
@@ -835,7 +862,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);
     }
@@ -853,10 +880,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) {
@@ -865,6 +893,7 @@ public:
                 xchg_stats->appendRcvd(packet);
             }
         }
+        return(sent_packet);
     }
 
     /// \brief Return minumum delay between sent and received packet.
@@ -999,6 +1028,33 @@ 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 Get time period since the start of test.
+    ///
+    /// Calculate dna return period since the test start. This
+    /// can be specifically helpful when calculating packet
+    /// exchange rates.
+    ///
+    /// \return test period so far.
+    boost::posix_time::time_period getTestPeriod() const {
+        using namespace boost::posix_time;
+        time_period test_period(boot_time_,
+                                microsec_clock::universal_time());
+        return test_period;
+    }
+
     /// \brief Return name of the exchange.
     ///
     /// Method returns name of the specified exchange type.
@@ -1052,6 +1108,32 @@ public:
         }
     }
 
+    /// \brief Print intermediate statistics.
+    ///
+    /// Method prints intermediate statistics for all exchanges.
+    /// Statistics includes sent, received and dropped packets
+    /// counters.
+    void printIntermediateStats() const {
+        std::ostringstream stream_sent;
+        std::ostringstream stream_rcvd;
+        std::ostringstream stream_drops;
+        std::string sep("");
+        for (ExchangesMapIterator it = exchanges_.begin();
+             it != exchanges_.end(); ++it) {
+
+            if (it != exchanges_.begin()) {
+                sep = "/";
+            }
+            stream_sent << sep << it->second->getSentPacketsNum();
+            stream_rcvd << sep << it->second->getRcvdPacketsNum();
+            stream_drops << sep << it->second->getDroppedPacketsNum();
+        }
+        std::cout << "sent: " << stream_sent.str() 
+                  << "; received: " << stream_rcvd.str()
+                  << "; drops: " << stream_drops.str()
+                  << std::endl;
+    }
+
     /// \brief Print timestamps of all packets.
     ///
     /// Method prints timestamps of all sent and received
@@ -1129,6 +1211,8 @@ private:
     /// for extended period of time and many packets have to be
     /// archived.
     bool archive_enabled_;
+
+    boost::posix_time::ptime boot_time_; ///< Time when test is started.
 };
 
 } // namespace perfdhcp

+ 8 - 0
tests/tools/perfdhcp/templates/Makefile.am

@@ -0,0 +1,8 @@
+SUBDIRS = .
+
+perfdhcpdir = $(pkgdatadir)
+perfdhcp_DATA = discover-example.hex request4-example.hex \
+                solicit-example.hex request6-example.hex
+
+EXTRA_DIST = discover-example.hex request4-example.hex
+EXTRA_DIST += solicit-example.hex request6-example.hex

File diff suppressed because it is too large
+ 1 - 0
tests/tools/perfdhcp/templates/discover-example.hex


File diff suppressed because it is too large
+ 1 - 0
tests/tools/perfdhcp/templates/request4-example.hex


+ 1 - 0
tests/tools/perfdhcp/templates/request6-example.hex

@@ -0,0 +1 @@
+03da30c60001000e0001000117cf8e76000c010203060002000e0001000117cf8a5c080027a87b3400030028000000010000000a0000000e0005001820010db800010000000000000001b568000000be000000c8000800020000

+ 1 - 0
tests/tools/perfdhcp/templates/solicit-example.hex

@@ -0,0 +1 @@
+015f4e650001000e0001000117cf8e76000c010203040003000c0000000100000e01000015180006000400170018000800020000

File diff suppressed because it is too large
+ 1682 - 0
tests/tools/perfdhcp/test_control.cc


+ 803 - 0
tests/tools/perfdhcp/test_control.h

@@ -0,0 +1,803 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __TEST_CONTROL_H
+#define __TEST_CONTROL_H
+
+#include <string>
+#include <vector>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/function.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <dhcp/iface_mgr.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+
+#include "stats_mgr.h"
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief Test Control class.
+///
+/// This class is responsible for executing DHCP performance
+/// test end to end.
+///
+/// Option factory functions are registered using
+/// \ref dhcp::LibDHCP::OptionFactoryRegister. Registered factory functions
+/// provide a way to create options of the same type in the same way.
+///  When new option instance is needed the corresponding factory
+/// function is called to create it. This is done by calling
+/// \ref dhcp::Option::factory with DHCP message type specified as one of
+///  parameters. Some of the parameters passed to factory function
+/// may be ignored (e.g. option buffer).
+/// Please note that naming convention for factory functions within this
+/// class is as follows:
+/// - factoryABC4 - factory function for DHCPv4 option,
+/// - factoryDEF6 - factory function for DHCPv6 option,
+/// - factoryGHI - factory function that can be used to create either
+/// DHCPv4 or DHCPv6 option.
+class TestControl : public boost::noncopyable {
+public:
+
+    /// Default transaction id offset.
+    static const size_t DHCPV4_TRANSID_OFFSET = 4;
+    /// Default offset of MAC's last octet.
+    static const size_t DHCPV4_RANDOMIZATION_OFFSET = 35;
+    /// Default elapsed time offset.
+    static const size_t DHCPV4_ELAPSED_TIME_OFFSET = 8;
+    /// Default server id offset.
+    static const size_t DHCPV4_SERVERID_OFFSET = 54;
+    /// Default requested ip offset.
+    static const size_t DHCPV4_REQUESTED_IP_OFFSET = 240;
+    /// Default DHCPV6 transaction id offset.
+    static const size_t DHCPV6_TRANSID_OFFSET = 1;
+    /// Default DHCPV6 randomization offset (last octet of DUID)
+    static const size_t DHCPV6_RANDOMIZATION_OFFSET = 21;
+    /// Default DHCPV6 elapsed time offset.
+    static const size_t DHCPV6_ELAPSED_TIME_OFFSET = 84;
+    /// Default DHCPV6 server id offset.
+    static const size_t DHCPV6_SERVERID_OFFSET = 22;
+    /// Default DHCPV6 IA_NA offset.
+    static const size_t DHCPV6_IA_NA_OFFSET = 40;
+
+    /// Statistics Manager for DHCPv4.
+    typedef StatsMgr<dhcp::Pkt4> StatsMgr4;
+    /// Pointer to Statistics Manager for DHCPv4;
+    typedef boost::shared_ptr<StatsMgr4> StatsMgr4Ptr;
+    /// Statictics Manager for DHCPv6.
+    typedef StatsMgr<dhcp::Pkt6> StatsMgr6;
+    /// Pointer to Statistics Manager for DHCPv6.
+    typedef boost::shared_ptr<StatsMgr6> StatsMgr6Ptr;
+    /// Packet exchange type.
+    typedef StatsMgr<>::ExchangeType ExchangeType;
+    /// Packet template buffer.
+    typedef std::vector<uint8_t> TemplateBuffer;
+    /// Packet template buffers list.
+    typedef std::vector<TemplateBuffer> TemplateBufferCollection;
+
+    /// \brief Socket wrapper structure.
+    ///
+    /// This is the wrapper that holds descriptor of the socket
+    /// used to run DHCP test. The wrapped socket is closed in
+    /// the destructor. This prevents resource leaks when when
+    /// function that created the socket ends (normally or
+    /// when exception occurs). This structure extends parent
+    /// structure with new field ifindex_ that holds interface
+    /// index where socket is bound to.
+    struct TestControlSocket : public dhcp::IfaceMgr::SocketInfo {
+        /// Interface index.
+        uint16_t ifindex_;
+        /// Is socket valid. It will not be valid if the provided socket
+        /// descriptor does not point to valid socket.
+        bool valid_;
+
+        /// \brief Constructor of socket wrapper class.
+        ///
+        /// This constructor uses provided socket descriptor to
+        /// find the name of the interface where socket has been
+        /// bound to. If provided socket descriptor is invalid then
+        /// valid_ field is set to false;
+        ///
+        /// \param socket socket descriptor.
+        TestControlSocket(const int socket);
+
+        /// \brief Destriuctor of the socket wrapper class.
+        ///
+        /// Destructor closes wrapped socket.
+        ~TestControlSocket();
+
+    private:
+        /// \brief Initialize socket data.
+        ///
+        /// This method initializes members of the class that Interface
+        /// Manager holds: interface name, local address.
+        ///
+        /// \throw isc::BadValue if interface for specified socket
+        /// descriptor does not exist.
+        void initSocketData();
+    };
+
+    /// \brief Number generator class.
+    ///
+    /// This is default numbers generator class. The member function is
+    /// used to generate uint32_t values. Other generator classes should
+    /// derive from this one to implement generation algorithms
+    /// (e.g. sequencial or based on random function).
+    class NumberGenerator {
+    public:
+        /// \brief Generate number.
+        ///
+        /// \return Generate number.
+        virtual uint32_t generate() = 0;
+    };
+
+    /// The default generator pointer.
+    typedef boost::shared_ptr<NumberGenerator> NumberGeneratorPtr;
+
+    /// \brief Sequencial numbers generatorc class.
+    class SequencialGenerator : public NumberGenerator {
+    public:
+        /// \brief Constructor.
+        ///
+        /// \param range maximum number generated. If 0 is given then
+        /// range defaults to maximym uint32_t value.
+        SequencialGenerator(uint32_t range = 0xFFFFFFFF) :
+            NumberGenerator(),
+            num_(0),
+            range_(range) {
+            if (range_ == 0) {
+                range_ = 0xFFFFFFFF;
+            }
+        }
+
+        /// \brief Generate number sequencialy.
+        ///
+        /// \return generated number.
+        virtual uint32_t generate() {
+            uint32_t num = num_;
+            num_ = (num_ + 1) % range_;
+            return (num);
+        }
+    private:
+        uint32_t num_;   ///< Current number.
+        uint32_t range_; ///< Maximum number generated.
+    };
+
+    /// \brief Length of the Ethernet HW address (MAC) in bytes.
+    ///
+    /// \todo Make this variable length as there are cases when HW
+    /// address is longer than this (e.g. 20 bytes).
+    static const uint8_t HW_ETHER_LEN = 6;
+
+    /// TestControl is a singleton class. This method returns reference
+    /// to its sole instance.
+    ///
+    /// \return the only existing instance of test control
+    static TestControl& instance();
+
+    /// brief\ Run performance test.
+    ///
+    /// Method runs whole performance test. Command line options must
+    /// be parsed prior to running this function. Othewise function will
+    /// throw exception.
+    ///
+    /// \throw isc::InvalidOperation if command line options are not parsed.
+    /// \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(const NumberGeneratorPtr& generator) {
+        transid_gen_.reset();
+        transid_gen_ = generator;
+    }
+
+    /// \brief Set new MAC address generator.
+    ///
+    /// Set numbers generator that will be used to generate various
+    /// MAC addresses to simulate number of clients.
+    ///
+    /// \param generator object to be used.
+    void setMacAddrGenerator(const NumberGeneratorPtr& generator) {
+        macaddr_gen_.reset();
+        macaddr_gen_ = generator;
+    }
+
+    // We would really like following methods and members to be private but
+    // they have to be accessible for unit-testing. Another, possibly better,
+    // solution is to make this class friend of test class but this is not
+    // what's followed in other classes.
+protected:
+    /// \brief Default constructor.
+    ///
+    /// Default constructor is protected as the object can be created
+    /// only via \ref instance method.
+    TestControl();
+
+    /// \brief Check if test exit condtitions fulfilled.
+    ///
+    /// Method checks if the test exit conditions are fulfiled.
+    /// Exit conditions are checked periodically from the
+    /// main loop. Program should break the main loop when
+    /// this method returns true. It is calling function
+    /// responsibility to break main loop gracefully and
+    /// cleanup after test execution.
+    ///
+    /// \return true if any of the exit conditions is fulfiled.
+    bool checkExitConditions() const;
+
+    /// \brief Factory function to create DHCPv6 ELAPSED_TIME option.
+    ///
+    /// This factory function creates DHCPv6 ELAPSED_TIME option instance.
+    /// If empty buffer is passed the option buffer will be initialized
+    /// to length 2 and values will be initialized to zeros. Otherwise
+    /// function will initialize option buffer with values in passed buffer.
+    ///
+    /// \param u universe (ignored)
+    /// \param type option-type (ignored).
+    /// \param buf option-buffer containing option content (2 bytes) or
+    /// empty buffer if option content has to be set to default (0) value.
+    /// \throw if elapsed time buffer size is neither 2 nor 0.
+    /// \return instance o the option.
+    static dhcp::OptionPtr
+    factoryElapsedTime6(dhcp::Option::Universe u,
+                        uint16_t type,
+                        const dhcp::OptionBuffer& buf);
+
+    /// \brief Factory function to create generic option.
+    ///
+    /// This factory function creates option with specified universe,
+    /// type and buf. It does not have any additional logic validating
+    /// the buffer contents, size  etc.
+    ///
+    /// \param u universe (V6 or V4).
+    /// \param type option-type (ignored).
+    /// \param buf option-buffer.
+    /// \return instance o the option.
+    static dhcp::OptionPtr factoryGeneric(dhcp::Option::Universe u,
+                                          uint16_t type,
+                                          const dhcp::OptionBuffer& buf);
+
+    /// \brief Factory function to create IA_NA option.
+    ///
+    /// This factory function creates DHCPv6 IA_NA option instance.
+    ///
+    /// \todo add support for IA Address options.
+    ///
+    /// \param u universe (ignored).
+    /// \param type option-type (ignored).
+    /// \param buf option-buffer carrying IANA suboptions.
+    /// \return instance of IA_NA option.
+    static dhcp::OptionPtr factoryIana6(dhcp::Option::Universe u,
+                                        uint16_t type,
+                                        const dhcp::OptionBuffer& buf);
+
+    /// \brief Factory function to create DHCPv6 ORO option.
+    ///
+    /// This factory function creates DHCPv6 Option Request Option instance.
+    /// The created option will contain the following set of requested options:
+    /// - D6O_NAME_SERVERS
+    /// - D6O_DOMAIN_SEARCH
+    ///
+    /// \param u universe (ignored).
+    /// \param type option-type (ignored).
+    /// \param buf option-buffer (ignored).
+    /// \return instance of ORO option.
+    static dhcp::OptionPtr
+    factoryOptionRequestOption6(dhcp::Option::Universe u,
+                                uint16_t type,
+                                const dhcp::OptionBuffer& buf);
+
+    /// \brief Factory function to create DHCPv6 RAPID_COMMIT option instance.
+    ///
+    /// This factory function creates DHCPv6 RAPID_COMMIT option instance.
+    /// The buffer passed to this option must be empty because option does
+    /// not have any payload.
+    ///
+    /// \param u universe (ignored).
+    /// \param type option-type (ignored).
+    /// \param buf option-buffer (ignored).
+    /// \return instance of RAPID_COMMIT option..
+    static dhcp::OptionPtr factoryRapidCommit6(dhcp::Option::Universe u,
+                                               uint16_t type,
+                                               const dhcp::OptionBuffer& buf);
+
+
+    /// \brief Factory function to create DHCPv4 Request List option.
+    ///
+    /// This factory function creayes DHCPv4 PARAMETER_REQUEST_LIST option
+    /// instance with the following set of requested options:
+    /// - DHO_SUBNET_MASK,
+    /// - DHO_BROADCAST_ADDRESS,
+    /// - DHO_TIME_OFFSET,
+    /// - DHO_ROUTERS,
+    /// - DHO_DOMAIN_NAME,
+    /// - DHO_DOMAIN_NAME_SERVERS,
+    /// - DHO_HOST_NAME.
+    ///
+    /// \param u universe (ignored).
+    /// \param type option-type (ignored).
+    /// \param buf option-buffer (ignored).
+    /// \return instance o the generic option.
+     static dhcp::OptionPtr factoryRequestList4(dhcp::Option::Universe u,
+                                               uint16_t type,
+                                               const dhcp::OptionBuffer& buf);
+
+    /// \brief Generate DUID.
+    ///
+    /// Method generates unique DUID. The number of DUIDs it can generate
+    /// depends on the number of simulated clients, which is specified
+    /// from the command line. It uses \ref CommandOptions object to retrieve
+    /// number of clients. Since the last six octets of DUID are constructed
+    /// from the MAC address, this function uses \ref generateMacAddress
+    /// internally to randomize the DUID.
+    ///
+    /// \todo add support for other types of DUID.
+    ///
+    /// \param [out] randomized number of bytes randomized (initial value
+    /// is ignored).
+    /// \throw isc::BadValue if \ref generateMacAddress throws.
+    /// \return vector representing DUID.
+    std::vector<uint8_t> generateDuid(uint8_t& randomized) const;
+
+    /// \brief Generate MAC address.
+    ///
+    /// This method generates MAC address. The number of unique
+    /// MAC addresses it can generate is determined by the number
+    /// simulated DHCP clients specified from command line. It uses
+    /// \ref CommandOptions object to retrieve number of clients.
+    /// Based on this the random value is generated and added to
+    /// the MAC address template (default MAC address).
+    ///
+    /// \param [out] randomized number of bytes randomized (initial
+    /// value is ignored).
+    /// \throw isc::BadValue if MAC address template (default or specified
+    /// from the command line) has invalid size (expected 6 octets).
+    /// \return generated MAC address.
+    std::vector<uint8_t> generateMacAddress(uint8_t& randomized) const;
+
+    /// \brief generate transaction id.
+    ///
+    /// Generate transaction id value (32-bit for DHCPv4,
+    /// 24-bit for DHCPv6).
+    ///
+    /// \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
+    /// as possible to satisfy expected rate. Calculation used here
+    /// is based on current time, due time calculated with
+    /// \ref updateSendDue function and expected rate.
+    ///
+    /// \return number of exchanges to be started immediately.
+    uint64_t getNextExchangesNum() const;
+
+    /// \brief Return template buffer.
+    ///
+    /// Method returns template buffer at specified index.
+    ///
+    /// \param idx index of template buffer.
+    /// \throw isc::OutOfRange if buffer index out of bounds.
+    /// \return reference to template buffer.
+    TemplateBuffer getTemplateBuffer(const size_t idx) const;
+
+    /// \brief Reads packet templates from files.
+    ///
+    /// Method iterates through all specified template files, reads
+    /// their content and stores it in class internal buffers. Template
+    /// file names are specified from the command line with -T option.
+    ///
+    /// \throw isc::BadValue if any of the template files does not exist
+    void initPacketTemplates();
+
+    /// \brief Initializes Statistics Manager.
+    ///
+    /// This function initializes Statistics Manager. If there is
+    /// the one initialized already it is released.
+    void initializeStatsMgr();
+
+    /// \brief Open socket to communicate with DHCP server.
+    ///
+    /// Method opens socket and binds it to local address. Function will
+    /// use either interface name, local address or server address
+    /// to create a socket, depending on what is available (specified
+    /// from the command line). If socket can't be created for any
+    /// reason, exception is thrown.
+    /// If destination address is broadcast (for DHCPv4) or multicast
+    /// (for DHCPv6) than broadcast or multicast option is set on
+    /// the socket. Opened socket is registered and managed by IfaceMgr.
+    ///
+    /// \throw isc::BadValue if socket can't be created for given
+    /// interface, local address or remote address.
+    /// \throw isc::InvalidOperation if broadcast option can't be
+    /// set for the v4 socket or if multicast option cat't be set
+    /// for the v6 socket.
+    /// \throw isc::Unexpected if interal unexpected error occured.
+    /// \return socket descriptor.
+    int openSocket() const;
+
+    /// \brief Print intermediate statistics.
+    ///
+    /// Print brief statistics regarding number of sent packets,
+    /// received packets and dropped packets so far.
+    void printIntermediateStats();
+
+    /// \brief Print rate statistics.
+    ///
+    /// Method print packet exchange rate statistics.
+    void printRate() const;
+
+    /// \brief Print performance statistics.
+    ///
+    /// Method prints performance statistics.
+    /// \throws isc::InvalidOperation if Statistics Manager was
+    /// not initialized.
+    void printStats() const;
+
+    /// \brief Process received DHCPv4 packet.
+    ///
+    /// Method performs processing of the received DHCPv4 packet,
+    /// updates statistics and responds to the server if required,
+    /// e.g. when OFFER packet arrives, this function will initiate
+    /// REQUEST message to the server.
+    ///
+    /// \warning this method does not check if provided socket is
+    /// valid (specifically if v4 socket for received v4 packet).
+    ///
+    /// \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.
+    void processReceivedPacket4(const TestControlSocket& socket,
+                                const dhcp::Pkt4Ptr& pkt4);
+
+    /// \brief Process received DHCPv6 packet.
+    ///
+    /// Method performs processing of the received 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.
+    ///
+    /// \warning this method does not check if provided socket is
+    /// valid (specifically if v4 socket for received v4 packet).
+    ///
+    /// \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.
+    void processReceivedPacket6(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.
+    /// This function will call \ref receivePacket4 or
+    /// \ref receivePacket6 depending if DHCPv4 or DHCPv6 packet
+    /// has arrived.
+    ///
+    /// \warning this method does not check if provided socket is
+    /// valid. Ensure that it is valid prior to calling it.
+    ///
+    /// \param socket socket to be used.
+    /// \throw isc::BadValue if unknown message type received.
+    /// \throw isc::Unexpected if unexpected error occured.
+    void receivePackets(const TestControlSocket& socket);
+
+    /// \brief Register option factory functions for DHCPv4
+    ///
+    /// Method registers option factory functions for DHCPv4.
+    /// These functions are called to create instances of DHCPv4
+    /// options. Call \ref dhcp::Option::factory to invoke factory
+    /// function for particular option. Don't use this function directly.
+    /// Use \ref registerOptionFactories instead.
+    void registerOptionFactories4() const;
+
+    /// \brief Register option factory functions for DHCPv6
+    ///
+    /// Method registers option factory functions for DHCPv6.
+    /// These functions are called to create instances of DHCPv6
+    /// options. Call \ref dhcp::Option::factory to invoke factory
+    /// function for particular option. Don't use this function directly.
+    /// Use \ref registerOptionFactories instead.
+    void registerOptionFactories6() const;
+
+    /// \brief Register option factory functions for DHCPv4 or DHCPv6.
+    ///
+    /// Method registers option factory functions for DHCPv4 or DHCPv6,
+    /// depending in whch mode test is currently running.
+    void registerOptionFactories() const;
+
+
+    /// \brief Resets internal state of the object.
+    ///
+    /// Method resets internal state of the object. It has to be
+    /// called before new test is started.
+    void reset();
+
+    /// \brief Send DHCPv4 DISCOVER message.
+    ///
+    /// Method creates and sends DHCPv4 DISCOVER message to the server
+    /// with the following options:
+    /// - MESSAGE_TYPE set to DHCPDISCOVER
+    /// - PARAMETER_REQUEST_LIST with the same list of requested options
+    /// as described in \ref factoryRequestList4.
+    /// The transaction id and MAC address are randomly generated for
+    /// the message. Range of unique MAC addresses generated depends
+    /// on the number of clients specified from the command line.
+    /// Copy of sent packet is stored in the stats_mgr4_ object to
+    /// update statistics.
+    ///
+    /// \param socket socket to be used to send the message.
+    /// \param preload preload mode, packets not included in statistics.
+    /// \throw isc::Unexpected if failed to create new packet instance.
+    /// \throw isc::BadValue if MAC address has invalid length.
+    void sendDiscover4(const TestControlSocket& socket,
+                       const bool preload = false);
+
+    /// \brief Send DHCPv4 DISCOVER message from template.
+    ///
+    /// Method sends DHCPv4 DISCOVER message from template. The
+    /// template data is exepcted to be in binary format. Provided
+    /// buffer is copied and parts of it are replaced with actual
+    /// data (e.g. MAC address, transaction id etc.).
+    /// Copy of sent packet is stored in the stats_mgr4_ object to
+    /// update statistics.
+    ///
+    /// \param socket socket to be used to send the message.
+    /// \param template_buf buffer holding template packet.
+    /// \param preload preload mode, packets not included in statistics.
+    /// \throw isc::OutOfRange if randomization offset is out of bounds.
+    void sendDiscover4(const TestControlSocket& socket,
+                       const std::vector<uint8_t>& template_buf,
+                       const bool preload = false);
+
+    /// \brief Send DHCPv4 REQUEST message.
+    ///
+    /// Method creates and sends DHCPv4 REQUEST message to the server.
+    /// Copy of sent packet is stored in the stats_mgr4_ object to
+    /// update statistics.
+    ///
+    /// \param socket socket to be used to send message.
+    /// \param discover_pkt4 DISCOVER packet sent.
+    /// \param offer_pkt4 OFFER packet object.
+    /// \throw isc::Unexpected if unexpected error occured.
+    /// \throw isc::InvalidOperation if Statistics Manager has not been
+    /// initialized.
+    void sendRequest4(const TestControlSocket& socket,
+                      const dhcp::Pkt4Ptr& discover_pkt4,
+                      const dhcp::Pkt4Ptr& offer_pkt4);
+
+    /// \brief Send DHCPv4 REQUEST message from template.
+    ///
+    /// Method sends DHCPv4 REQUEST message from template.
+    /// Copy of sent packet is stored in the stats_mgr4_ object to
+    /// update statistics.
+    ///
+    /// \param socket socket to be used to send message.
+    /// \param template_buf buffer holding template packet.
+    /// \param discover_pkt4 DISCOVER packet sent.
+    /// \param offer_pkt4 OFFER packet received.
+    void sendRequest4(const TestControlSocket& socket,
+                      const std::vector<uint8_t>& template_buf,
+                      const dhcp::Pkt4Ptr& discover_pkt4,
+                      const dhcp::Pkt4Ptr& offer_pkt4);
+
+    /// \brief Send DHCPv6 REQUEST message.
+    ///
+    /// Method creates and sends DHCPv6 REQUEST message to the server
+    /// with the following options:
+    /// - D6O_ELAPSED_TIME
+    /// - D6O_CLIENTID
+    /// - D6O_SERVERID
+    /// Copy of sent packet is stored in the stats_mgr6_ object to
+    /// update statistics.
+    ///
+    /// \param socket socket to be used to send message.
+    /// \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& advertise_pkt6);
+
+    /// \brief Send DHCPv6 REQUEST message from template.
+    ///
+    /// Method sends DHCPv6 REQUEST message from template.
+    /// Copy of sent packet is stored in the stats_mgr6_ object to
+    /// update statistics.
+    ///
+    /// \param socket socket to be used to send message.
+    /// \param template_buf packet template buffer.
+    /// \param advertise_pkt6 ADVERTISE packet object.
+    void sendRequest6(const TestControlSocket& socket,
+                      const std::vector<uint8_t>& template_buf,
+                      const dhcp::Pkt6Ptr& advertise_pkt6);
+
+    /// \brief Send DHCPv6 SOLICIT message.
+    ///
+    /// Method creates and sends DHCPv6 SOLICIT message to the server
+    /// with the following options:
+    /// - D6O_ELAPSED_TIME,
+    /// - D6O_RAPID_COMMIT if rapid commit is requested in command line,
+    /// - D6O_CLIENTID,
+    /// - D6O_ORO (Option Request Option),
+    /// - D6O_IA_NA.
+    /// Copy of sent packet is stored in the stats_mgr6_ object to
+    /// update statistics.
+    ///
+    /// \param socket socket to be used to send the message.
+    /// \param preload mode, packets not included in statistics.
+    /// \throw isc::Unexpected if failed to create new packet instance.
+    void sendSolicit6(const TestControlSocket& socket,
+                      const bool preload = false);
+
+    /// \brief Send DHCPv6 SOLICIT message from template.
+    ///
+    /// Method sends DHCPv6 SOLICIT message from template.
+    /// Copy of sent packet is stored in the stats_mgr6_ object to
+    /// update statistics.
+    ///
+    /// \param socket socket to be used to send the message.
+    /// \param template_buf packet template buffer.
+    /// \param preload mode, packets not included in statistics.
+    void sendSolicit6(const TestControlSocket& socket,
+                      const std::vector<uint8_t>& template_buf,
+                      const bool preload = false);
+
+    /// \brief Set default DHCPv4 packet parameters.
+    ///
+    /// This method sets default parameters on the DHCPv4 packet:
+    /// - interface name,
+    /// - local port = 68 (DHCP client port),
+    /// - remote port = 67 (DHCP server port),
+    /// - server's address,
+    /// - GIADDR = local address where socket is bound to,
+    /// - hops = 1 (pretending that we are a relay)
+    ///
+    /// \param socket socket used to send the packet.
+    /// \param pkt reference to packet to be configured.
+    void setDefaults4(const TestControlSocket& socket,
+                      const dhcp::Pkt4Ptr& pkt);
+
+    /// \brief Set default DHCPv6 packet parameters.
+    ///
+    /// This method sets default parameters on the DHCPv6 packet:
+    /// - interface name,
+    /// - interface index,
+    /// - local port,
+    /// - remote port,
+    /// - local address,
+    /// - remote address (server).
+    ///
+    /// \param socket socket used to send the packet.
+    /// \param pkt reference to packet to be configured.
+    void setDefaults6(const TestControlSocket& socket,
+                      const dhcp::Pkt6Ptr& pkt);
+
+    /// \brief Find if diagnostic flag has been set.
+    ///
+    /// \param diag diagnostic flag (a,e,i,s,r,t,T).
+    /// \return true if diagnostics flag has been set.
+    bool testDiags(const char diag) const;
+
+    /// \brief Update due time to initiate next chunk of exchanges.
+    ///
+    /// Method updates due time to initiate next chunk of exchanges.
+    /// Function takes current time, last sent packet's time and
+    /// expected rate in its calculations.
+    void updateSendDue();
+
+private:
+
+    /// \brief Convert binary value to hex string.
+    ///
+    /// \todo Consider moving this function to src/lib/util.
+    ///
+    /// \param b byte to convert.
+    /// \return hex string.
+    std::string byte2Hex(const uint8_t b) const;
+
+    /// \brief Calculate elapsed time between two packets.
+    ///
+    /// \param T Pkt4Ptr or Pkt6Ptr class.
+    /// \param pkt1 first packet.
+    /// \param pkt2 second packet.
+    /// \throw InvalidOperation if packet timestamps are invalid.
+    /// \return elapsed time in milliseconds between pkt1 and pkt2.
+    template<class T>
+    uint32_t getElapsedTime(const T& pkt1, const T& pkt2);
+
+    /// \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;
+
+    /// \brief Handle interrupt signal.
+    ///
+    /// Function sets flag indicating that program has been
+    /// interupted.
+    ///
+    /// \param sig signal (ignored)
+    static void handleInterrupt(int sig);
+
+    /// \brief Print main diagnostics data.
+    ///
+    /// Method prints main diagnostics data.
+    void printDiagnostics() const;
+
+    /// \brief Read DHCP message template from file.
+    ///
+    /// Method reads DHCP message template from file and
+    /// converts it to binary format. Read data is appended
+    /// to template_buffers_ vector.
+    void readPacketTemplate(const std::string& file_name);
+
+    /// \brief Convert vector in hexadecimal string.
+    ///
+    /// \todo Consider moving this function to src/lib/util.
+    ///
+    /// \param vec vector to be converted.
+    /// \param separator separator.
+    std::string vector2Hex(const std::vector<uint8_t>& vec,
+                           const std::string& separator = "") const;
+
+    boost::posix_time::ptime send_due_;    ///< Due time to initiate next chunk
+                                           ///< of exchanges.
+    boost::posix_time::ptime last_sent_;   ///< Indicates when the last exchange
+                                           /// was initiated.
+
+    boost::posix_time::ptime last_report_; ///< Last intermediate report time.
+
+    StatsMgr4Ptr stats_mgr4_;  ///< Statistics Manager 4.
+    StatsMgr6Ptr stats_mgr6_;  ///< Statistics Manager 6.
+
+    NumberGeneratorPtr transid_gen_; ///< Transaction id generator.
+    NumberGeneratorPtr macaddr_gen_; ///< Numbers generator for MAC address.
+
+    /// Buffer holiding server id received in first packet
+    dhcp::OptionBuffer first_packet_serverid_;
+
+    /// Packet template buffers.
+    TemplateBufferCollection template_buffers_;
+
+    static bool interrupted_;  ///< Is program interrupted.
+};
+
+} // namespace perfdhcp
+} // namespace isc
+
+#endif // __COMMAND_OPTIONS_H

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

@@ -22,10 +22,13 @@ run_unittests_SOURCES += perf_pkt6_unittest.cc
 run_unittests_SOURCES += perf_pkt4_unittest.cc
 run_unittests_SOURCES += localized_option_unittest.cc
 run_unittests_SOURCES += stats_mgr_unittest.cc
+run_unittests_SOURCES += test_control_unittest.cc
+run_unittests_SOURCES += command_options_helper.h
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/command_options.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/pkt_transform.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt6.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt4.cc
+run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/test_control.cc
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)

+ 138 - 0
tests/tools/perfdhcp/tests/command_options_helper.h

@@ -0,0 +1,138 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __COMMAND_OPTIONS_HELPER_H
+#define __COMMAND_OPTIONS_HELPER_H
+
+#include <string>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+#include "../command_options.h"
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief Command Options Helper class.
+///
+/// This helper class can be shared between unit tests that
+/// need to initialize CommandOptions objects and feed it with
+/// specific command line. The command line can be given as a
+/// string representing program name, options and arguments.
+/// The static method exposed by this class can be used to
+/// tokenize this string into array of C-strings that are later
+/// consumed by \ref CommandOptions::parse. The state of the
+/// CommandOptions object is reset every time the process
+/// function is invoked. Also, when command line parsing is
+/// ended the array of C-string is freed from the memory.
+class CommandOptionsHelper {
+public:
+
+    /// \brief Wrapper class for allocated argv[] array.
+    ///
+    /// This class wraps allocated char** array and ensures that memory
+    /// allocated for this array is freed at the end o the scope.
+    class ArgvPtr {
+    public:
+        /// \brief Constructor.
+        ///
+        /// \param argv array of C-strings.
+        /// \param number of C-strings in the array.
+        ArgvPtr(char** argv, int argc) : argv_(argv), argc_(argc) { }
+
+        /// \brief Destructor.
+        ///
+        /// Dealocates wrapped array of C-strings.
+        ~ArgvPtr() {
+            if (argv_ != NULL) {
+                for(int i = 0; i < argc_; ++i) {
+                    free(argv_[i]);
+                    argv_[i] = NULL;
+                }
+                free(argv_);
+            }
+        }
+
+        /// \brief Return the array of C-strings.
+        ///
+        /// \return array of C-strings.
+        char** getArgv() const { return (argv_); }
+
+        /// \brief Return C-strings counter.
+        ///
+        /// \return C-strings counter.
+        int getArgc() const { return(argc_); }
+
+    public:
+        char** argv_; ///< array of C-strings being wrapped.
+        int argc_;    ///< number of C-strings.
+    };
+
+    /// \brief Parse command line provided as string.
+    ///
+    /// Method transforms the string representing command line
+    /// to the array of C-strings consumed by the
+    /// \ref CommandOptions::parse function and performs
+    /// parsing.
+    ///
+    /// \param cmdline command line provided as single string.
+    static void process(const std::string& cmdline) {
+        CommandOptions& opt = CommandOptions::instance();
+        int argc = 0;
+        char** argv = tokenizeString(cmdline, argc);
+        ArgvPtr args(argv, argc);
+        opt.reset();
+        opt.parse(args.getArgc(), args.getArgv());
+    }
+
+private:
+
+    /// \brief Split string to the array of C-strings.
+    ///
+    /// \param text_to_split string to be splited.
+    /// \param [out] num number of substrings returned.
+    /// \return array of C-strings created from split.
+    static char** tokenizeString(const std::string& text_to_split, int& num) {
+        char** results = NULL;
+        // Tokenization with std streams
+        std::stringstream text_stream(text_to_split);
+        // Iterators to be used for tokenization
+        std::istream_iterator<std::string> text_iterator(text_stream);
+        std::istream_iterator<std::string> text_end;
+        // Tokenize string (space is a separator) using begin and end iteratos
+        std::vector<std::string> tokens(text_iterator, text_end);
+
+        if (tokens.size() > 0) {
+            // Allocate array of C-strings where we will store tokens
+            results = static_cast<char**>(malloc(tokens.size() * sizeof(char*)));
+            if (results == NULL) {
+                isc_throw(Unexpected, "unable to allocate array of c-strings");
+            }
+            // Store tokens in C-strings array
+            for (int i = 0; i < tokens.size(); ++i) {
+                char* cs = static_cast<char*>(malloc(tokens[i].length() + 1));
+                strcpy(cs, tokens[i].c_str());
+                results[i] = cs;
+            }
+            // Return number of tokens to calling function
+            num = tokens.size();
+        }
+        return results;
+    }
+};
+
+} // namespace isc::perfdhcp
+} // namespace isc
+
+#endif // __COMMAND_OPTIONS_HELPER_H

+ 280 - 100
tests/tools/perfdhcp/tests/command_options_unittest.cc

@@ -16,14 +16,17 @@
 #include <stdint.h>
 #include <string>
 #include <gtest/gtest.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
 
-#include "../command_options.h"
+#include <dhcp/iface_mgr.h>
+#include <exceptions/exceptions.h>
 
-#include "exceptions/exceptions.h"
+#include "../command_options.h"
 
 using namespace std;
 using namespace isc;
 using namespace isc::perfdhcp;
+using namespace boost::posix_time;
 
 /// \brief Test Fixture Class
 ///
@@ -62,7 +65,7 @@ protected:
     /// Check if initialized values are correct
     void checkDefaults() {
         CommandOptions& opt = CommandOptions::instance();
-        process("perfdhcp");
+        process("perfdhcp 192.168.0.1");
         EXPECT_EQ(4, opt.getIpVersion());
         EXPECT_EQ(CommandOptions::DORA_SARR, opt.getExchangeMode());
         EXPECT_EQ(0, opt.getRate());
@@ -70,11 +73,45 @@ protected:
         EXPECT_EQ(0, opt.getClientsNum());
 
         // default mac
-        uint8_t mac[6] = { 0x00, 0x0C, 0x01, 0x02, 0x03, 0x04 };
-        std::vector<uint8_t> v1 = opt.getMacPrefix();
+        const uint8_t mac[6] = { 0x00, 0x0C, 0x01, 0x02, 0x03, 0x04 };
+        std::vector<uint8_t> v1 = opt.getMacTemplate();
         ASSERT_EQ(6, v1.size());
         EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac));
 
+        // Check if DUID is initialized. The DUID-LLT is expected
+        // to start with DUID_LLT value of 1 and hardware ethernet
+        // type equal to 1 (HWETHER_TYPE).
+        const uint8_t duid_llt_and_hw[4] = { 0x0, 0x1, 0x0, 0x1 };
+        // We assume DUID-LLT length 14. This includes 4 octets of
+        // DUID_LLT value, two octets of hardware type, 4 octets
+        // of time value and 6 octets of variable link layer (MAC)
+        // address.
+        const int duid_llt_size = 14;
+        // DUID is not given from the command line but it is supposed
+        // to be initialized by the CommandOptions private method
+        // generateDuidTemplate().
+        std::vector<uint8_t> v2 = opt.getDuidTemplate();
+        ASSERT_EQ(duid_llt_size, opt.getDuidTemplate().size());
+        EXPECT_TRUE(std::equal(v2.begin(), v2.begin() + 4,
+                               duid_llt_and_hw));
+        // Check time field contents.
+        ptime now = microsec_clock::universal_time();
+        ptime duid_epoch(from_iso_string("20000101T000000"));
+        time_period period(duid_epoch, now);
+        uint32_t duration_sec = period.length().total_seconds();
+        // Read time from the template generated.
+        uint32_t duration_from_template = 0;
+        memcpy(&duration_from_template, &v2[4], 4);
+        duration_from_template = htonl(duration_from_template);
+        // In special cases, we may have overflow in time field
+        // so we give ourselves the margin of 10 seconds here.
+        // If time value has been set more then 10 seconds back
+        // it is safe to compare it with the time value generated
+        // from now.
+        if (duration_from_template > 10) {
+            EXPECT_GE(duration_sec, duration_from_template);
+        }
+
         EXPECT_EQ(0, opt.getBase().size());
         EXPECT_EQ(0, opt.getNumRequests().size());
         EXPECT_EQ(0, opt.getPeriod());
@@ -104,7 +141,7 @@ protected:
         EXPECT_GT(0, opt.getRequestedIpOffset());
         EXPECT_EQ("", opt.getDiags());
         EXPECT_EQ("", opt.getWrapped());
-        EXPECT_EQ("", opt.getServerName());
+        EXPECT_EQ("192.168.0.1", opt.getServerName());
     }
 
     /// \brief Split string to array of C-strings
@@ -145,153 +182,210 @@ protected:
 };
 
 TEST_F(CommandOptionsTest, Defaults) {
-    process("perfdhcp");
+    process("perfdhcp all");
     checkDefaults();
 }
 
 TEST_F(CommandOptionsTest, UseFirst) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -1 -B -l ethx");
+    process("perfdhcp -1 -B -l ethx all");
     EXPECT_TRUE(opt.isUseFirst());
 }
 TEST_F(CommandOptionsTest, IpVersion) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -6 -l ethx -c -i");
+    process("perfdhcp -6 -l ethx -c -i all");
     EXPECT_EQ(6, opt.getIpVersion());
     EXPECT_EQ("ethx", opt.getLocalName());
     EXPECT_TRUE(opt.isRapidCommit());
     EXPECT_FALSE(opt.isBroadcast());
-    process("perfdhcp -4 -B -l ethx");
+    process("perfdhcp -4 -B -l ethx all");
     EXPECT_EQ(4, opt.getIpVersion());
     EXPECT_TRUE(opt.isBroadcast());
     EXPECT_FALSE(opt.isRapidCommit());
 
     // Negative test cases
     // -4 and -6 must not coexist
-    EXPECT_THROW(process("perfdhcp -4 -6 -l ethx"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -4 -6 -l ethx all"), isc::InvalidParameter);
     // -6 and -B must not coexist
-    EXPECT_THROW(process("perfdhcp -6 -B -l ethx"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -6 -B -l ethx all"), isc::InvalidParameter);
     // -c and -4 (default) must not coexist
-    EXPECT_THROW(process("perfdhcp -c -l ethx"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -c -l ethx all"), isc::InvalidParameter);
 }
 
 TEST_F(CommandOptionsTest, Rate) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -4 -r 10 -l ethx");
+    process("perfdhcp -4 -r 10 -l ethx all");
     EXPECT_EQ(10, opt.getRate());
 
     // Negative test cases
     // Rate must not be 0
-    EXPECT_THROW(process("perfdhcp -4 -r 0 -l ethx"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -4 -r 0 -l ethx all"),
+                 isc::InvalidParameter);
     // -r must be specified to use -n, -p and -D
-    EXPECT_THROW(process("perfdhcp -6 -t 5 -l ethx"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -4 -n 150 -l ethx"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -6 -p 120 -l ethx"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -4 -D 1400 -l ethx"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -6 -t 5 -l ethx all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -4 -n 150 -l ethx all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -6 -p 120 -l ethx all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -4 -D 1400 -l ethx all"),
+                 isc::InvalidParameter);
 }
 
 TEST_F(CommandOptionsTest, ReportDelay) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -r 100 -t 17 -l ethx");
+    process("perfdhcp -r 100 -t 17 -l ethx all");
     EXPECT_EQ(17, opt.getReportDelay());
 
     // Negative test cases
     // -t must be positive integer
-    EXPECT_THROW(process("perfdhcp -r 10 -t -8 -l ethx"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -r 10 -t 0 -l ethx"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -r 10 -t s -l ethx"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -r 10 -t -8 -l ethx all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -r 10 -t 0 -l ethx all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -r 10 -t s -l ethx all"),
+                 isc::InvalidParameter);
 }
 
 TEST_F(CommandOptionsTest, ClientsNum) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -R 200 -l ethx");
+    process("perfdhcp -R 200 -l ethx all");
     EXPECT_EQ(200, opt.getClientsNum());
-    process("perfdhcp -R 0 -l ethx");
+    process("perfdhcp -R 0 -l ethx all");
     EXPECT_EQ(0, opt.getClientsNum());
 
     // Negative test cases
     // Number of clients must be non-negative integer
-    EXPECT_THROW(process("perfdhcp -R -5 -l ethx"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -R gs -l ethx"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -R -5 -l ethx all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -R gs -l ethx all"),
+                 isc::InvalidParameter);
 }
 
 TEST_F(CommandOptionsTest, Base) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -6 -b MAC=10::20::30::40::50::60 -l ethx -b duiD=1AB7F5670901FF");
     uint8_t mac[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60 };
-    uint8_t duid[7] = { 0x1A, 0xB7, 0xF5, 0x67, 0x09, 0x01, 0xFF };
-
-    // Test Mac
-    std::vector<uint8_t> v1 = opt.getMacPrefix();
-    ASSERT_EQ(6, v1.size());
+    uint8_t duid[14] = {  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+                          0x01, 0x01, 0x01, 0x10, 0x11, 0x1F, 0x14 };
+    // Test DUID and MAC together.
+    EXPECT_NO_THROW(process("perfdhcp -b DUID=0101010101010101010110111F14"
+                            " -b MAC=10::20::30::40::50::60"
+                            " -l 127.0.0.1 all"));
+    std::vector<uint8_t> v1 = opt.getMacTemplate();
+    std::vector<uint8_t> v2 = opt.getDuidTemplate();
+    v2 = opt.getDuidTemplate();
     EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac));
-    // "3x" is invalid value in MAC address
-    EXPECT_THROW(process("perfdhcp -b mac=10::2::3x::4::5::6 -l ethx"), isc::InvalidParameter);
+    EXPECT_TRUE(std::equal(v2.begin(), v2.end(), duid));
+    // Test valid DUID.
+    EXPECT_NO_THROW(
+        process("perfdhcp -b duid=0101010101010101010110111F14 -l 127.0.0.1 all")
+    );
 
-    // Test DUID
-    std::vector<uint8_t> v2 = opt.getDuidPrefix();
     ASSERT_EQ(sizeof(duid) / sizeof(uint8_t), v2.size());
     EXPECT_TRUE(std::equal(v2.begin(), v2.end(), duid));
-    // "t" is invalid digit in DUID
-    EXPECT_THROW(process("perfdhcp -6 -l ethx -b duiD=1AB7Ft670901FF"), isc::InvalidParameter);
-
-    // Some more negative test cases
+    // Test mix of upper/lower case letters.
+    EXPECT_NO_THROW(process("perfdhcp -b DuiD=0101010101010101010110111F14"
+                            " -b Mac=10::20::30::40::50::60"
+                            " -l 127.0.0.1 all"));
+    v1 = opt.getMacTemplate();
+    v2 = opt.getDuidTemplate();
+    EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac));
+    EXPECT_TRUE(std::equal(v2.begin(), v2.end(), duid));
+    // Use "ether" instead of "mac".
+    EXPECT_NO_THROW(process("perfdhcp -b ether=10::20::30::40::50::60"
+                            " -l 127.0.0.1 all"));
+    v1 = opt.getMacTemplate();
+    EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac));
+    // Use "ETHER" in upper case.
+    EXPECT_NO_THROW(process("perfdhcp -b ETHER=10::20::30::40::50::60"
+                            " -l 127.0.0.1 all"));
+    v1 = opt.getMacTemplate();
+    EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac));
+    // "t" is invalid character in DUID
+    EXPECT_THROW(process("perfdhcp -6 -l ethx -b "
+                         "duid=010101010101010101t110111F14 all"),
+                 isc::InvalidParameter);
+    // "3x" is invalid value in MAC address
+    EXPECT_THROW(process("perfdhcp -b mac=10::2::3x::4::5::6 -l ethx all"),
+                 isc::InvalidParameter);
     // Base is not specified
-    EXPECT_THROW(process("perfdhcp -b -l ethx"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -b -l ethx all"),
+                 isc::InvalidParameter);
     // Typo: should be mac= instead of mc=
-    EXPECT_THROW(process("perfdhcp -l ethx -b mc=00:01:02:03::04:05"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -l ethx -b mc=00:01:02:03::04:05 all"),
+                 isc::InvalidParameter);
+    // Too short DUID (< 6).
+    EXPECT_THROW(process("perfdhcp -l ethx -b duid=00010203 all"),
+                 isc::InvalidParameter);
+    // Odd number of digits.
+    EXPECT_THROW(process("perfdhcp -l ethx -b duid=000102030405060 all"),
+                 isc::InvalidParameter);
+    // Too short MAC (!= 6).
+    EXPECT_THROW(process("perfdhcp -l ethx -b mac=00:01:02:04 all"),
+                 isc::InvalidParameter);
 }
 
 TEST_F(CommandOptionsTest, DropTime) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -l ethx -d 12");
+    process("perfdhcp -l ethx -d 12 all");
     ASSERT_EQ(2, opt.getDropTime().size());
     EXPECT_DOUBLE_EQ(12, opt.getDropTime()[0]);
     EXPECT_DOUBLE_EQ(1, opt.getDropTime()[1]);
 
-    process("perfdhcp -l ethx -d 2 -d 4.7");
+    process("perfdhcp -l ethx -d 2 -d 4.7 all");
     ASSERT_EQ(2, opt.getDropTime().size());
     EXPECT_DOUBLE_EQ(2, opt.getDropTime()[0]);
     EXPECT_DOUBLE_EQ(4.7, opt.getDropTime()[1]);
 
     // Negative test cases
     // Drop time must not be negative
-    EXPECT_THROW(process("perfdhcp -l ethx -d -2 -d 4.7"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -l ethx -d -9.1 -d 0"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -l ethx -d -2 -d 4.7 all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -l ethx -d -9.1 -d 0 all"),
+                 isc::InvalidParameter);
 }
 
 TEST_F(CommandOptionsTest, TimeOffset) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -l ethx -T file1.x -T file2.x -E 4");
+    process("perfdhcp -l ethx -T file1.x -T file2.x -E 4 all");
     EXPECT_EQ(4, opt.getElapsedTimeOffset());
 
     // Negative test cases
     // Argument -E must be used with -T
-    EXPECT_THROW(process("perfdhcp -l ethx -E 3 -i"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -l ethx -E 3 -i all"),
+                 isc::InvalidParameter);
     // Value in -E not specified
-    EXPECT_THROW(process("perfdhcp -l ethx -T file.x -E -i"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -l ethx -T file.x -E -i all"),
+                 isc::InvalidParameter);
     // Value for -E must not be negative
-    EXPECT_THROW(process("perfdhcp -l ethx -E -3 -T file.x"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -l ethx -E -3 -T file.x all"),
+                 isc::InvalidParameter);
 }
 
 TEST_F(CommandOptionsTest, ExchangeMode) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -l ethx -i");
+    process("perfdhcp -l ethx -i all");
     EXPECT_EQ(CommandOptions::DO_SA, opt.getExchangeMode());
 
     // Negative test cases
     // No template file specified
-    EXPECT_THROW(process("perfdhcp -i -l ethx -X 3"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -i -l ethx -X 3 all"),
+                 isc::InvalidParameter);
     // Offsets can't be used in simple exchanges (-i)
-    EXPECT_THROW(process("perfdhcp -i -l ethx -O 2 -T file.x"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -i -l ethx -E 3 -T file.x"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -i -l ethx -S 1 -T file.x"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -i -l ethx -I 2 -T file.x"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -i -l ethx -O 2 -T file.x all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -i -l ethx -E 3 -T file.x all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -i -l ethx -S 1 -T file.x all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -i -l ethx -I 2 -T file.x all"),
+                 isc::InvalidParameter);
 }
 
 TEST_F(CommandOptionsTest, Offsets) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -E5 -4 -I 2 -S3 -O 30 -X7 -l ethx -X3 -T file1.x -T file2.x");
+    process("perfdhcp -E5 -4 -I 2 -S3 -O 30 -X7 -l ethx "
+            "-X3 -T file1.x -T file2.x all");
     EXPECT_EQ(2, opt.getRequestedIpOffset());
     EXPECT_EQ(5, opt.getElapsedTimeOffset());
     EXPECT_EQ(3, opt.getServerIdOffset());
@@ -304,151 +398,237 @@ TEST_F(CommandOptionsTest, Offsets) {
 
     // Negative test cases
     // IP offset/IA_NA offset must be positive
-    EXPECT_THROW(process("perfdhcp -6 -I 0 -l ethx"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -6 -I -4 -l ethx"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -6 -I 0 -l ethx all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -6 -I -4 -l ethx all"),
+                 isc::InvalidParameter);
 
     // TODO - other negative cases
 }
 
 TEST_F(CommandOptionsTest, LocalPort) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -l ethx -L 2000");
+    process("perfdhcp -l ethx -L 2000 all");
     EXPECT_EQ(2000, opt.getLocalPort());
 
     // Negative test cases
     // Local port must be between 0..65535
-    EXPECT_THROW(process("perfdhcp -l ethx -L -2"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -l ethx -L"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -l ethx -L 65540"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -l ethx -L -2 all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -l ethx -L all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -l ethx -L 65540 all"),
+                 isc::InvalidParameter);
 }
 
 TEST_F(CommandOptionsTest, Preload) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -1 -P 3 -l ethx");
+    process("perfdhcp -1 -P 3 -l ethx all");
     EXPECT_EQ(3, opt.getPreload());
 
     // Negative test cases
     // Number of preload packages must not be negative integer
-    EXPECT_THROW(process("perfdhcp -P -1 -l ethx"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -P -3 -l ethx"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -P -1 -l ethx all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -P -3 -l ethx all"),
+                 isc::InvalidParameter);
 }
 
 TEST_F(CommandOptionsTest, Seed) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -6 -P 2 -s 23 -l ethx");
+    process("perfdhcp -6 -P 2 -s 23 -l ethx all");
     EXPECT_EQ(23, opt.getSeed());
     EXPECT_TRUE(opt.isSeeded());
 
-    process("perfdhcp -6 -P 2 -s 0 -l ethx");
+    process("perfdhcp -6 -P 2 -s 0 -l ethx all");
     EXPECT_EQ(0, opt.getSeed());
     EXPECT_FALSE(opt.isSeeded());
 
     // Negtaive test cases
     // Seed must be non-negative integer
-    EXPECT_THROW(process("perfdhcp -6 -P 2 -s -5 -l ethx"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -6 -P 2 -s -l ethx"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -6 -P 2 -s -5 -l ethx all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -6 -P 2 -s -l ethx all"),
+                 isc::InvalidParameter);
 }
 
 TEST_F(CommandOptionsTest, TemplateFiles) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -T file1.x -l ethx");
+    process("perfdhcp -T file1.x -l ethx all");
     ASSERT_EQ(1, opt.getTemplateFiles().size());
     EXPECT_EQ("file1.x", opt.getTemplateFiles()[0]);
 
-    process("perfdhcp -T file1.x -s 12 -w start -T file2.x -4 -l ethx");
+    process("perfdhcp -T file1.x -s 12 -w start -T file2.x -4 -l ethx all");
     ASSERT_EQ(2, opt.getTemplateFiles().size());
     EXPECT_EQ("file1.x", opt.getTemplateFiles()[0]);
     EXPECT_EQ("file2.x", opt.getTemplateFiles()[1]);
 
     // Negative test cases
     // No template file specified
-    EXPECT_THROW(process("perfdhcp -s 12 -l ethx -T"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -s 12 -T -l ethx all"),
+                 isc::InvalidParameter);
     // Too many template files specified
-    EXPECT_THROW(process("perfdhcp -s 12 -l ethx -T file.x -T file.x -T file.x"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -s 12 -l ethx -T file.x "
+                         "-T file.x -T file.x all"),
+                 isc::InvalidParameter);
 }
 
 TEST_F(CommandOptionsTest, Wrapped) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -B -w start -i -l ethx");
+    process("perfdhcp -B -w start -i -l ethx all");
     EXPECT_EQ("start", opt.getWrapped());
 
     // Negative test cases
     // Missing command after -w, expected start/stop
-    EXPECT_THROW(process("perfdhcp -B -i -l ethx -w"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -B -i -l ethx -w all"),
+                 isc::InvalidParameter);
 }
 
 TEST_F(CommandOptionsTest, Diagnostics) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -l ethx -i -x asTe");
+    process("perfdhcp -l ethx -i -x asTe all");
     EXPECT_EQ("asTe", opt.getDiags());
 
     // Negative test cases
     // No diagnostics string specified
-    EXPECT_THROW(process("perfdhcp -l ethx -i -x"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -l ethx -i -x all"),
+                 isc::InvalidParameter);
 }
 
 TEST_F(CommandOptionsTest, Aggressivity) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -a 10 -l 192.168.0.1");
+    process("perfdhcp -a 10 -l 192.168.0.1 all");
     EXPECT_EQ(10, opt.getAggressivity());
 
     // Negative test cases
     // Aggressivity must be non negative integer
-    EXPECT_THROW(process("perfdhcp -l ethx -a 0"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -l ethx -a"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -a -2 -l ethx -a 3"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -l ethx -a 0 all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -l ethx -a all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -a -2 -l ethx -a 3 all"),
+                 isc::InvalidParameter);
 }
 
 TEST_F(CommandOptionsTest, MaxDrop) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -D 25 -l ethx -r 10");
+    process("perfdhcp -D 25 -l ethx -r 10 all");
     EXPECT_EQ(25, opt.getMaxDrop()[0]);
-    process("perfdhcp -D 25 -l ethx -D 15 -r 10");
+    process("perfdhcp -D 25 -l ethx -D 15 -r 10 all");
     EXPECT_EQ(25, opt.getMaxDrop()[0]);
     EXPECT_EQ(15, opt.getMaxDrop()[1]);
 
-    process("perfdhcp -D 15% -l ethx -r 10");
+    process("perfdhcp -D 15% -l ethx -r 10 all");
     EXPECT_EQ(15, opt.getMaxDropPercentage()[0]);
-    process("perfdhcp -D 15% -D25% -l ethx -r 10");
+    process("perfdhcp -D 15% -D25% -l ethx -r 10 all");
     EXPECT_EQ(15, opt.getMaxDropPercentage()[0]);
     EXPECT_EQ(25, opt.getMaxDropPercentage()[1]);
-    process("perfdhcp -D 1% -D 99% -l ethx -r 10");
+    process("perfdhcp -D 1% -D 99% -l ethx -r 10 all");
     EXPECT_EQ(1, opt.getMaxDropPercentage()[0]);
     EXPECT_EQ(99, opt.getMaxDropPercentage()[1]);
 
     // Negative test cases
     // Too many -D<value> options
-    EXPECT_THROW(process("perfdhcp -D 0% -D 1 -l ethx -r20 -D 3"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -D 0% -D 1 -l ethx -r20 -D 3 all"),
+                 isc::InvalidParameter);
     // Too many -D<value%> options
-    EXPECT_THROW(process("perfdhcp -D 99% -D 13% -l ethx -r20 -D 10%"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -D 99% -D 13% -l ethx -r20 -D 10% all"),
+                 isc::InvalidParameter);
     // Percentage is out of bounds
-    EXPECT_THROW(process("perfdhcp -D101% -D 13% -l ethx -r20"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -D0% -D 13% -l ethx -r20"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -D101% -D 13% -l ethx -r20 all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -D0% -D 13% -l ethx -r20 all"),
+                 isc::InvalidParameter);
 }
 
 TEST_F(CommandOptionsTest, NumRequest) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -n 1000 -r 10 -l ethx");
+    process("perfdhcp -n 1000 -r 10 -l ethx all");
     EXPECT_EQ(1000, opt.getNumRequests()[0]);
-    process("perfdhcp -n 5 -r 10 -n 500 -l ethx");
+    process("perfdhcp -n 5 -r 10 -n 500 -l ethx all");
     EXPECT_EQ(5, opt.getNumRequests()[0]);
     EXPECT_EQ(500, opt.getNumRequests()[1]);
 
     // Negative test cases
     // Too many -n<value> parameters, expected maximum 2
-    EXPECT_THROW(process("perfdhcp -n 1 -n 2 -l ethx -n3 -r 20"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -n 1 -n 2 -l ethx -n3 -r 20 all"),
+                 isc::InvalidParameter);
     // Num request must be positive integer
-    EXPECT_THROW(process("perfdhcp -n 1 -n -22 -l ethx -r 10"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -n 0 -l ethx -r 10"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -n 1 -n -22 -l ethx -r 10 all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -n 0 -l ethx -r 10 all"),
+                 isc::InvalidParameter);
 }
 
 TEST_F(CommandOptionsTest, Period) {
     CommandOptions& opt = CommandOptions::instance();
-    process("perfdhcp -p 120 -l ethx -r 100");
+    process("perfdhcp -p 120 -l ethx -r 100 all");
     EXPECT_EQ(120, opt.getPeriod());
 
     // Negative test cases
     // Test period must be positive integer
-    EXPECT_THROW(process("perfdhcp -p 0 -l ethx -r 50"), isc::InvalidParameter);
-    EXPECT_THROW(process("perfdhcp -p -3 -l ethx -r 50"), isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -p 0 -l ethx -r 50 all"),
+                 isc::InvalidParameter);
+    EXPECT_THROW(process("perfdhcp -p -3 -l ethx -r 50 all"),
+                 isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Interface) {
+    // In order to make this test portable we need to know
+    // at least one interface name on OS where test is run.
+    // Interface Manager has ability to detect interfaces.
+    // Altough we don't call initIsInterface explicitely
+    // here it is called by CommandOptions object interally
+    // so this function is covered by the test.
+    dhcp::IfaceMgr& iface_mgr = dhcp::IfaceMgr::instance();
+    const dhcp::IfaceMgr::IfaceCollection& ifaces = iface_mgr.getIfaces();
+    std::string iface_name;
+    CommandOptions& opt = CommandOptions::instance();
+    // The local loopback interface should be available. 
+    // If no interface have been found for any reason we should
+    // not fail this test.
+    if (ifaces.size() > 0) {
+        // Get the name of the interface we detected.
+        iface_name = ifaces.begin()->getName();
+        // Use the name in the command parser.
+        ASSERT_NO_THROW(process("perfdhcp -4 -l " + iface_name + " abc"));
+        // We expect that command parser will detect that argument
+        // specified along with '-l' is the interface name.
+        EXPECT_TRUE(opt.isInterface());
+
+        // If neither interface nor server is specified then
+        // exception is expected to be thrown.
+        EXPECT_THROW(process("perfdhcp -4"), isc::InvalidParameter);
+    }
+}
+
+TEST_F(CommandOptionsTest, Server) {
+    CommandOptions& opt = CommandOptions::instance();
+    // There is at least server parameter needed. If server is not
+    // specified the local interface must be specified.
+    // The server value equal to 'all' means use broadcast.
+    ASSERT_NO_THROW(process("perfdhcp all"));
+    // Once command line is parsed we expect that server name is
+    // set to broadcast address because 'all' was specified.
+    EXPECT_TRUE(opt.isBroadcast());
+    // The broadcast address is 255.255.255.255.
+    EXPECT_EQ("255.255.255.255", opt.getServerName());
+
+    // When all is specified for DHCPv6 mode we expect
+    // FF02::1:2 as a server name which means All DHCP
+    // servers and relay agents in local network segment
+    ASSERT_NO_THROW(process("perfdhcp -6 all"));
+    EXPECT_EQ("FF02::1:2", opt.getServerName());
+
+    // When server='servers' in DHCPv6 mode we expect
+    // FF05::1:3 as server name which means All DHCP
+    // servers in local network.
+    ASSERT_NO_THROW(process("perfdhcp -6 servers"));
+    EXPECT_EQ("FF05::1:3", opt.getServerName());
+
+    // If server name is neither 'all' nor 'servers'
+    // the given argument value is expected to be
+    // returned.
+    ASSERT_NO_THROW(process("perfdhcp -6 abc"));
+    EXPECT_EQ("abc", opt.getServerName());
 }

+ 46 - 0
tests/tools/perfdhcp/tests/perf_pkt4_unittest.cc

@@ -381,4 +381,50 @@ TEST_F(PerfPkt4Test, UnpackTransactionId) {
     EXPECT_FALSE(pkt2->rawUnpack());
 }
 
+TEST_F(PerfPkt4Test, Writes) {
+    // Initialize intput buffer with 260 elements set to value 1.
+    dhcp::OptionBuffer in_data(260, 1);
+    // Initialize buffer to be used for write: 1,2,3,4,...,9
+    dhcp::OptionBuffer write_buf(10);
+    for (int i = 0; i < write_buf.size(); ++i) {
+        write_buf[i] = i;
+    }
+    // Create packet from the input buffer.
+    const size_t transid_offset = 4;
+    boost::scoped_ptr<PerfPkt4> pkt1(new PerfPkt4(&in_data[0],
+                                                  in_data.size(),
+                                                  transid_offset));
+    // Write numbers 4,5,6,7 to the packet's input buffer at position 10.
+    pkt1->writeAt(10, write_buf.begin() + 3, write_buf.begin() + 7);
+    // We have to pack data to output buffer here because Pkt4 provides no
+    // way to retrieve input buffer. If we pack data it will go to
+    // output buffer that has getter available.
+    ASSERT_TRUE(pkt1->rawPack());
+    const util::OutputBuffer& out_buf = pkt1->getBuffer();
+    ASSERT_EQ(in_data.size(), out_buf.getLength());
+    // Verify that 4,5,6,7 has been written to the packet's buffer.
+    const char* out_data = static_cast<const char*>(out_buf.getData());
+    EXPECT_TRUE(std::equal(write_buf.begin() + 3, write_buf.begin() + 7,
+                          out_data + 10));
+    // Write 1 octet (0x51) at position 10.
+    pkt1->writeValueAt<uint8_t>(10, 0x51);
+    ASSERT_TRUE(pkt1->rawPack());
+    ASSERT_EQ(in_data.size(), pkt1->getBuffer().getLength());
+    EXPECT_EQ(0x51, pkt1->getBuffer()[10]);
+    // Write 2 octets (0x5251) at position 20.
+    pkt1->writeValueAt<uint16_t>(20, 0x5251);
+    ASSERT_TRUE(pkt1->rawPack());
+    ASSERT_EQ(in_data.size(), pkt1->getBuffer().getLength());
+    EXPECT_EQ(0x52, pkt1->getBuffer()[20]);
+    EXPECT_EQ(0x51, pkt1->getBuffer()[21]);
+    // Write 4 octets (0x54535251) at position 30.
+    pkt1->writeValueAt<uint32_t>(30, 0x54535251);
+    ASSERT_TRUE(pkt1->rawPack());
+    ASSERT_EQ(in_data.size(), pkt1->getBuffer().getLength());
+    EXPECT_EQ(0x54, pkt1->getBuffer()[30]);
+    EXPECT_EQ(0x53, pkt1->getBuffer()[31]);
+    EXPECT_EQ(0x52, pkt1->getBuffer()[32]);
+    EXPECT_EQ(0x51, pkt1->getBuffer()[33]);
+}
+
 }

+ 2 - 2
tests/tools/perfdhcp/tests/stats_mgr_unittest.cc

@@ -387,13 +387,13 @@ TEST_F(StatsMgrTest, CustomCounters) {
     // Increment one of the counters 10 times.
     const uint64_t tooshort_num = 10;
     for (uint64_t i = 0; i < tooshort_num; ++i) {
-        stats_mgr->IncrementCounter(too_short_key);
+        stats_mgr->incrementCounter(too_short_key);
     }
 
     // Increment another counter by 5 times.
     const uint64_t toolate_num = 5;
     for (uint64_t i = 0; i < toolate_num; ++i) {
-        stats_mgr->IncrementCounter(too_late_key);
+        stats_mgr->incrementCounter(too_late_key);
     }
 
     // Check counter's current value and name.

File diff suppressed because it is too large
+ 1011 - 0
tests/tools/perfdhcp/tests/test_control_unittest.cc