diff --git a/configure.ac b/configure.ac index e46e182ce3..2ead4b7de2 100644 --- a/configure.ac +++ b/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 diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc index 90f6353622..7df809e776 100644 --- a/src/lib/dhcp/iface_mgr.cc +++ b/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_); } diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h index 36ad0eada2..d7d9f0607a 100644 --- a/src/lib/dhcp/iface_mgr.h +++ b/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 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); - /// socket used to sending data - /// TODO: this should be protected - SocketCollection sockets_; + /// @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 + SocketCollection sockets_; + /// network interface name std::string name_; diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc index c054a4b0a5..22cd47bb6e 100644 --- a/src/lib/dhcp/libdhcp++.cc +++ b/src/lib/dhcp/libdhcp++.cc @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "config.h" #include @@ -34,6 +35,31 @@ std::map LibDHCP::v4factories_; std::map 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; diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h index c7935c8ff3..ae907012cc 100644 --- a/src/lib/dhcp/libdhcp++.h +++ b/src/lib/dhcp/libdhcp++.h @@ -25,6 +25,26 @@ namespace dhcp { class LibDHCP { public: + + /// Map of factory functions. + typedef std::map 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 v4factories_; + static FactoryMap v4factories_; /// pointers to factories that produce DHCPv6 options - static std::map v6factories_; + static FactoryMap v6factories_; }; } diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc index 0c71606b6c..fb441f93d1 100644 --- a/src/lib/dhcp/option.cc +++ b/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) { diff --git a/src/lib/dhcp/option.h b/src/lib/dhcp/option.h index 066296704e..080a869996 100644 --- a/src/lib/dhcp/option.h +++ b/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) diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc index 5562551a0e..ad8dba905b 100644 --- a/src/lib/dhcp/tests/iface_mgr_unittest.cc +++ b/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 ifacemgr(new NakedIfaceMgr()); + + // container for initialized socket descriptors + std::list 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::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::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 diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc index 7e18be6889..d17eb6537f 100644 --- a/src/lib/dhcp/tests/libdhcp++_unittest.cc +++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include #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 diff --git a/tests/tools/perfdhcp/Makefile.am b/tests/tools/perfdhcp/Makefile.am index 7c8064ea14..0532f27dcc 100644 --- a/tests/tools/perfdhcp/Makefile.am +++ b/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 diff --git a/tests/tools/perfdhcp/command_options.cc b/tests/tools/perfdhcp/command_options.cc index 09393bbc3d..24e7e790dc 100644 --- a/tests/tools/perfdhcp/command_options.cc +++ b/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 #include #include #include @@ -20,9 +21,11 @@ #include #include #include +#include -#include "exceptions/exceptions.h" - +#include +#include +#include #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 @@ -376,6 +412,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); @@ -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 duid_template; size_t found = base.find('='); check(found == std::string::npos, "expected -b format for duid is -b 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(ui)); + duid_template.push_back(static_cast(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 @@ -564,6 +648,98 @@ CommandOptions::nonEmptyString(const std::string& errmsg) const { return sarg; } +void +CommandOptions::printCommandLine() const { + std::cout << "IPv" << static_cast(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", @@ -691,7 +867,7 @@ CommandOptions::usage() const { void CommandOptions::version() const { - fprintf(stdout, "version 0.01\n"); + std::cout << "VERSION: " << VERSION << std::endl; } diff --git a/tests/tools/perfdhcp/command_options.h b/tests/tools/perfdhcp/command_options.h index 9196857d7c..b9e2b9e70e 100644 --- a/tests/tools/perfdhcp/command_options.h +++ b/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 getMacPrefix() const { return mac_prefix_; } + /// \return MAC address template to simulate different clients. + std::vector getMacTemplate() const { return mac_template_; } - /// \brief Returns DUID prefix + /// \brief Returns DUID template. /// - /// \return DUID prefix to simulate different clients - std::vector getDuidPrefix() const { return duid_prefix_; } + /// \return DUID template to simulate different clients. + std::vector 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 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 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 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 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 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 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 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 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 is wrong + /// \throw InvalidParameter if -R is wrong. void initClientsNum(); - /// \brief Decodes base provided with -b + /// \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. /// /// 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 + /// \brief Decodes base MAC address provided with -b. /// /// 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 + /// \brief Decodes base DUID provided with -b. /// - /// 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 Converts two-digit hexadecimal string to a byte + + /// \brief Generates DUID-LLT (based on link layer address). /// - /// \param hex_text Hexadecimal string e.g. AF - /// \throw isc::InvalidParameter if string does not represent hex byte + /// 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. + /// + /// \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 mac_prefix_; ///< MAC address prefix used to generate unique DUIDs + std::vector mac_template_; ///< MAC address template used to generate unique DUIDs ///< for simulated clients. - std::vector duid_prefix_; ///< DUID prefix used to generate unique DUIDs for + std::vector duid_template_; ///< DUID template used to generate unique DUIDs for ///< simulated clients std::vector base_; ///< Collection of base values specified with -b ///< options. Supported "bases" are mac= and duid= @@ -404,6 +422,7 @@ private: std::string wrapped_; ///< Wrapped command specified as -w. 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 diff --git a/tests/tools/perfdhcp/localized_option.h b/tests/tools/perfdhcp/localized_option.h index 5374684f89..336e08366e 100644 --- a/tests/tools/perfdhcp/localized_option.h +++ b/tests/tools/perfdhcp/localized_option.h @@ -16,6 +16,8 @@ #define __LOCALIZED_OPTION_H #include +#include +#include namespace isc { namespace perfdhcp { @@ -42,56 +44,30 @@ 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, but rather subset of it. - /// - /// \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. - LocalizedOption(dhcp::Option::Universe u, - uint16_t type, - dhcp::OptionBufferConstIter first, - dhcp::OptionBufferConstIter last) : - dhcp::Option(u, type, first, last), - offset_(0) { - } - - - /// \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, 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) @@ -102,9 +78,52 @@ public: LocalizedOption(dhcp::Option::Universe u, uint16_t type, dhcp::OptionBufferConstIter first, - dhcp::OptionBufferConstIter last, const size_t offset) : + dhcp::OptionBufferConstIter last, + const size_t offset = 0) : dhcp::Option(u, type, first, last), - offset_(offset) { + offset_(offset), option_valid_(true) { + } + + /// \brief Copy constructor, creates LocalizedOption from Option6IA. + /// + /// This copy constructor creates regular option from Option6IA. + /// The data from Option6IA data members are copied to + /// option buffer in appropriate sequence. + /// + /// \param opt_ia option to be copied. + /// \param offset location of the option in a packet. + LocalizedOption(const boost::shared_ptr& 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(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 diff --git a/tests/tools/perfdhcp/main.cc b/tests/tools/perfdhcp/main.cc new file mode 100644 index 0000000000..0c706a2b8c --- /dev/null +++ b/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 +#include + +#include +#include + +#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); +} + diff --git a/tests/tools/perfdhcp/perf_pkt4.cc b/tests/tools/perfdhcp/perf_pkt4.cc index 3f733afacc..3ccef94e98 100644 --- a/tests/tools/perfdhcp/perf_pkt4.cc +++ b/tests/tools/perfdhcp/perf_pkt4.cc @@ -16,7 +16,6 @@ #include #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::iterator first, + std::vector::iterator last) { + return (PktTransform::writeAt(data_, dest_pos, first, last)); +} + + + } // namespace perfdhcp } // namespace isc diff --git a/tests/tools/perfdhcp/perf_pkt4.h b/tests/tools/perfdhcp/perf_pkt4.h index f4cc440773..87c7bb0974 100644 --- a/tests/tools/perfdhcp/perf_pkt4.h +++ b/tests/tools/perfdhcp/perf_pkt4.h @@ -20,6 +20,7 @@ #include #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::iterator first, + std::vector::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 + void writeValueAt(size_t dest_pos, T val) { + PktTransform::writeValueAt(data_, dest_pos, val); + } + private: size_t transid_offset_; ///< transaction id offset }; +typedef boost::shared_ptr PerfPkt4Ptr; + } // namespace perfdhcp } // namespace isc diff --git a/tests/tools/perfdhcp/perf_pkt6.cc b/tests/tools/perfdhcp/perf_pkt6.cc index 24cfb931a9..56fe9dfd77 100644 --- a/tests/tools/perfdhcp/perf_pkt6.cc +++ b/tests/tools/perfdhcp/perf_pkt6.cc @@ -60,5 +60,13 @@ PerfPkt6::rawUnpack() { return (res); } +void +PerfPkt6::writeAt(size_t dest_pos, + std::vector::iterator first, + std::vector::iterator last) { + return (PktTransform::writeAt(data_, dest_pos, first, last)); +} + + } // namespace perfdhcp } // namespace isc diff --git a/tests/tools/perfdhcp/perf_pkt6.h b/tests/tools/perfdhcp/perf_pkt6.h index 94fe47bada..25fb4e53a4 100644 --- a/tests/tools/perfdhcp/perf_pkt6.h +++ b/tests/tools/perfdhcp/perf_pkt6.h @@ -20,6 +20,7 @@ #include #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::iterator first, + std::vector::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 + void writeValueAt(size_t dest_pos, T val) { + PktTransform::writeValueAt(data_, dest_pos, val); + } + private: size_t transid_offset_; ///< transaction id offset }; +typedef boost::shared_ptr PerfPkt6Ptr; + } // namespace perfdhcp } // namespace isc diff --git a/tests/tools/perfdhcp/pkt_transform.cc b/tests/tools/perfdhcp/pkt_transform.cc index 5ed39bfc5f..b1c64e2339 100644 --- a/tests/tools/perfdhcp/pkt_transform.cc +++ b/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 diff --git a/tests/tools/perfdhcp/pkt_transform.h b/tests/tools/perfdhcp/pkt_transform.h index 7fb19f48c4..1f57105720 100644 --- a/tests/tools/perfdhcp/pkt_transform.h +++ b/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::iterator first, + std::vector::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 + 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 diff --git a/tests/tools/perfdhcp/stats_mgr.h b/tests/tools/perfdhcp/stats_mgr.h index 245c69e3cf..a8dfa8b7bd 100644 --- a/tests/tools/perfdhcp/stats_mgr.h +++ b/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 +/// \param T class representing DHCPv4 or DHCPv6 packet. +template 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& packet) { + static uint32_t hashTransid(const boost::shared_ptr& packet) { if (!packet) { isc_throw(BadValue, "Packet is null"); } @@ -214,21 +214,33 @@ public: /// } /// \endcode typedef boost::multi_index_container< - boost::shared_ptr, + // Container holds shared_ptr or shared_ptr objects. + boost::shared_ptr, + // 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&, - 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 or + // shared_ptr as argument. + const boost::shared_ptr&, + // ... 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), - min_delay_(std::numeric_limits::max()), - max_delay_(0.), - sum_delay_(0.), - orphans_(0), - sum_delay_squared_(0.), - ordered_lookups_(0), - unordered_lookup_size_sum_(0), - unordered_lookups_(0), - sent_packets_num_(0), - rcvd_packets_num_(0), sent_packets_(), rcvd_packets_(), archived_packets_(), - archive_enabled_(archive_enabled) { + archive_enabled_(archive_enabled), + min_delay_(std::numeric_limits::max()), + max_delay_(0.), + sum_delay_(0.), + sum_delay_squared_(0.), + orphans_(0), + unordered_lookup_size_sum_(0), + unordered_lookups_(0), + ordered_lookups_(0), + sent_packets_num_(0), + 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& packet) { + void appendSent(const boost::shared_ptr& 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& packet) { + void appendRcvd(const boost::shared_ptr& 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& sent_packet, - const boost::shared_ptr& rcvd_packet) { + void updateDelays(const boost::shared_ptr& sent_packet, + const boost::shared_ptr& 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 matchPackets(const boost::shared_ptr& rcvd_packet) { + boost::shared_ptr + matchPackets(const boost::shared_ptr& 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()); + return(boost::shared_ptr()); } 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()); + return(boost::shared_ptr()); } // 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 sent_packet(*next_sent_); + boost::shared_ptr 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 rcvd_packet = *it; + boost::shared_ptr rcvd_packet = *it; PktListTransidHashIndex& idx = archived_packets_.template get<1>(); std::pairgetTransid() == rcvd_packet->getTransid()) { - boost::shared_ptr sent_packet = *it_archived; + boost::shared_ptr 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& packet) { + const boost::shared_ptr& 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& packet) { + boost::shared_ptr + passRcvdPacket(const ExchangeType xchg_type, + const boost::shared_ptr& packet) { ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); - boost::shared_ptr sent_packet + boost::shared_ptr 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 diff --git a/tests/tools/perfdhcp/templates/Makefile.am b/tests/tools/perfdhcp/templates/Makefile.am new file mode 100644 index 0000000000..4da2027903 --- /dev/null +++ b/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 diff --git a/tests/tools/perfdhcp/templates/discover-example.hex b/tests/tools/perfdhcp/templates/discover-example.hex new file mode 100644 index 0000000000..9a6e5ea53d --- /dev/null +++ b/tests/tools/perfdhcp/templates/discover-example.hex @@ -0,0 +1 @@ +01010601008b45d200000000000000000000000000000000ac100102000c0102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501013707011c02030f060cff \ No newline at end of file diff --git a/tests/tools/perfdhcp/templates/request4-example.hex b/tests/tools/perfdhcp/templates/request4-example.hex new file mode 100644 index 0000000000..32447d6ba8 --- /dev/null +++ b/tests/tools/perfdhcp/templates/request4-example.hex @@ -0,0 +1 @@ +01010601007b23f800000000000000000000000000000000ac100102000c0102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633204ac1001813501033604ac1001013707011c02030f060cff \ No newline at end of file diff --git a/tests/tools/perfdhcp/templates/request6-example.hex b/tests/tools/perfdhcp/templates/request6-example.hex new file mode 100644 index 0000000000..1e3e76f5e7 --- /dev/null +++ b/tests/tools/perfdhcp/templates/request6-example.hex @@ -0,0 +1 @@ +03da30c60001000e0001000117cf8e76000c010203060002000e0001000117cf8a5c080027a87b3400030028000000010000000a0000000e0005001820010db800010000000000000001b568000000be000000c8000800020000 \ No newline at end of file diff --git a/tests/tools/perfdhcp/templates/solicit-example.hex b/tests/tools/perfdhcp/templates/solicit-example.hex new file mode 100644 index 0000000000..41c5ad33a8 --- /dev/null +++ b/tests/tools/perfdhcp/templates/solicit-example.hex @@ -0,0 +1 @@ +015f4e650001000e0001000117cf8e76000c010203040003000c0000000100000e01000015180006000400170018000800020000 \ No newline at end of file diff --git a/tests/tools/perfdhcp/test_control.cc b/tests/tools/perfdhcp/test_control.cc new file mode 100644 index 0000000000..c154cf9ceb --- /dev/null +++ b/tests/tools/perfdhcp/test_control.cc @@ -0,0 +1,1682 @@ +// 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 +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include "test_control.h" +#include "command_options.h" +#include "perf_pkt4.h" +#include "perf_pkt6.h" + +using namespace std; +using namespace boost::posix_time; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::asiolink; + +namespace isc { +namespace perfdhcp { + +bool TestControl::interrupted_ = false; + +TestControl::TestControlSocket::TestControlSocket(const int socket) : + SocketInfo(socket, asiolink::IOAddress("127.0.0.1"), 0), + ifindex_(0), valid_(true) { + try { + initSocketData(); + } catch (const Exception&) { + valid_ = false; + } +} + +TestControl::TestControlSocket::~TestControlSocket() { + IfaceMgr::Iface* iface = IfaceMgr::instance().getIface(ifindex_); + if (iface) { + iface->delSocket(sockfd_); + } +} + +void +TestControl::TestControlSocket::initSocketData() { + const IfaceMgr::IfaceCollection& ifaces = + IfaceMgr::instance().getIfaces(); + for (IfaceMgr::IfaceCollection::const_iterator it = ifaces.begin(); + it != ifaces.end(); + ++it) { + const IfaceMgr::SocketCollection& socket_collection = + it->getSockets(); + for (IfaceMgr::SocketCollection::const_iterator s = + socket_collection.begin(); + s != socket_collection.end(); + ++s) { + if (s->sockfd_ == sockfd_) { + ifindex_ = it->getIndex(); + addr_ = s->addr_; + return; + } + } + } + isc_throw(BadValue, "interface for for specified socket " + "descriptor not found"); +} + +TestControl& +TestControl::instance() { + static TestControl test_control; + return (test_control); +} + +TestControl::TestControl() { + reset(); +} + +std::string +TestControl::byte2Hex(const uint8_t b) const { + const int b1 = b / 16; + const int b0 = b % 16; + ostringstream stream; + stream << std::hex << b1 << b0 << std::dec; + return (stream.str()); +} + +bool +TestControl::checkExitConditions() const { + if (interrupted_) { + return (true); + } + CommandOptions& options = CommandOptions::instance(); + bool test_period_reached = false; + // Check if test period passed. + if (options.getPeriod() != 0) { + if (options.getIpVersion() == 4) { + time_period period(stats_mgr4_->getTestPeriod()); + if (period.length().total_seconds() >= options.getPeriod()) { + test_period_reached = true; + } + } else if (options.getIpVersion() == 6) { + time_period period = stats_mgr6_->getTestPeriod(); + if (period.length().total_seconds() >= options.getPeriod()) { + test_period_reached = true; + } + } + } + if (test_period_reached) { + if (testDiags('e')) { + std::cout << "reached test-period." << std::endl; + } + return (true); + } + + bool max_requests = false; + // Check if we reached maximum number of DISCOVER/SOLICIT sent. + if (options.getNumRequests().size() > 0) { + if (options.getIpVersion() == 4) { + if (getSentPacketsNum(StatsMgr4::XCHG_DO) >= + options.getNumRequests()[0]) { + max_requests = true; + } + } else if (options.getIpVersion() == 6) { + if (stats_mgr6_->getSentPacketsNum(StatsMgr6::XCHG_SA) >= + options.getNumRequests()[0]) { + max_requests = true; + } + } + } + // Check if we reached maximum number REQUEST packets. + if (options.getNumRequests().size() > 1) { + if (options.getIpVersion() == 4) { + if (stats_mgr4_->getSentPacketsNum(StatsMgr4::XCHG_RA) >= + options.getNumRequests()[1]) { + max_requests = true; + } + } else if (options.getIpVersion() == 6) { + if (stats_mgr6_->getSentPacketsNum(StatsMgr6::XCHG_RR) >= + options.getNumRequests()[1]) { + max_requests = true; + } + } + } + if (max_requests) { + if (testDiags('e')) { + std::cout << "Reached max requests limit." << std::endl; + } + return (true); + } + + // Check if we reached maximum number of drops of OFFER/ADVERTISE packets. + bool max_drops = false; + if (options.getMaxDrop().size() > 0) { + if (options.getIpVersion() == 4) { + if (stats_mgr4_->getDroppedPacketsNum(StatsMgr4::XCHG_DO) >= + options.getMaxDrop()[0]) { + max_drops = true; + } + } else if (options.getIpVersion() == 6) { + if (stats_mgr6_->getDroppedPacketsNum(StatsMgr6::XCHG_SA) >= + options.getMaxDrop()[0]) { + max_drops = true; + } + } + } + // Check if we reached maximum number of drops of ACK/REPLY packets. + if (options.getMaxDrop().size() > 1) { + if (options.getIpVersion() == 4) { + if (stats_mgr4_->getDroppedPacketsNum(StatsMgr4::XCHG_RA) >= + options.getMaxDrop()[1]) { + max_drops = true; + } + } else if (options.getIpVersion() == 6) { + if (stats_mgr6_->getDroppedPacketsNum(StatsMgr6::XCHG_RR) >= + options.getMaxDrop()[1]) { + max_drops = true; + } + } + } + if (max_drops) { + if (testDiags('e')) { + std::cout << "Reached maximum drops number." << std::endl; + } + return (true); + } + + // Check if we reached maximum drops percentage of OFFER/ADVERTISE packets. + bool max_pdrops = false; + if (options.getMaxDropPercentage().size() > 0) { + if (options.getIpVersion() == 4) { + if ((stats_mgr4_->getSentPacketsNum(StatsMgr4::XCHG_DO) > 10) && + ((100. * stats_mgr4_->getDroppedPacketsNum(StatsMgr4::XCHG_DO) / + stats_mgr4_->getSentPacketsNum(StatsMgr4::XCHG_DO)) >= + options.getMaxDropPercentage()[0])) { + max_pdrops = true; + + } + } else if (options.getIpVersion() == 6) { + if ((stats_mgr6_->getSentPacketsNum(StatsMgr6::XCHG_SA) > 10) && + ((100. * stats_mgr6_->getDroppedPacketsNum(StatsMgr6::XCHG_SA) / + stats_mgr6_->getSentPacketsNum(StatsMgr6::XCHG_SA)) >= + options.getMaxDropPercentage()[0])) { + max_pdrops = true; + } + } + } + // Check if we reached maximum drops percentage of ACK/REPLY packets. + if (options.getMaxDropPercentage().size() > 1) { + if (options.getIpVersion() == 4) { + if ((stats_mgr4_->getSentPacketsNum(StatsMgr4::XCHG_RA) > 10) && + ((100. * stats_mgr4_->getDroppedPacketsNum(StatsMgr4::XCHG_RA) / + stats_mgr4_->getSentPacketsNum(StatsMgr4::XCHG_RA)) >= + options.getMaxDropPercentage()[1])) { + max_pdrops = true; + } + } else if (options.getIpVersion() == 6) { + if ((stats_mgr6_->getSentPacketsNum(StatsMgr6::XCHG_RR) > 10) && + ((100. * stats_mgr6_->getDroppedPacketsNum(StatsMgr6::XCHG_RR) / + stats_mgr6_->getSentPacketsNum(StatsMgr6::XCHG_RR)) >= + options.getMaxDropPercentage()[1])) { + max_pdrops = true; + } + } + } + if (max_pdrops) { + if (testDiags('e')) { + std::cout << "Reached maximum percentage of drops." << std::endl; + } + return (true); + } + return (false); +} + +OptionPtr +TestControl::factoryElapsedTime6(Option::Universe, uint16_t, + const OptionBuffer& buf) { + if (buf.size() == 2) { + return (OptionPtr(new Option(Option::V6, D6O_ELAPSED_TIME, buf))); + } else if (buf.size() == 0) { + return (OptionPtr(new Option(Option::V6, D6O_ELAPSED_TIME, + OptionBuffer(2, 0)))); + } + isc_throw(isc::BadValue, + "elapsed time option buffer size has to be 0 or 2"); +} + +OptionPtr +TestControl::factoryGeneric(Option::Universe u, uint16_t type, + const OptionBuffer& buf) { + OptionPtr opt(new Option(u, type, buf)); + return (opt); +} + +OptionPtr +TestControl::factoryIana6(Option::Universe, uint16_t, + const OptionBuffer& buf) { + // @todo allow different values of T1, T2 and IAID. + const uint8_t buf_array[] = { + 0, 0, 0, 1, // IAID = 1 + 0, 0, 3600 >> 8, 3600 & 0xff, // T1 = 3600 + 0, 0, 5400 >> 8, 5400 & 0xff, // T2 = 5400 + }; + OptionBuffer buf_ia_na(buf_array, buf_array + sizeof(buf_array)); + for (int i = 0; i < buf.size(); ++i) { + buf_ia_na.push_back(buf[i]); + } + return (OptionPtr(new Option(Option::V6, D6O_IA_NA, buf_ia_na))); +} + +OptionPtr +TestControl::factoryRapidCommit6(Option::Universe, uint16_t, + const OptionBuffer&) { + return (OptionPtr(new Option(Option::V6, D6O_RAPID_COMMIT, OptionBuffer()))); +} + +OptionPtr +TestControl::factoryOptionRequestOption6(Option::Universe, + uint16_t, + const OptionBuffer&) { + const uint8_t buf_array[] = { + 0, D6O_NAME_SERVERS, + 0, D6O_DOMAIN_SEARCH, + }; + OptionBuffer buf_with_options(buf_array, buf_array + sizeof(buf_array)); + return (OptionPtr(new Option(Option::V6, D6O_ORO, buf_with_options))); +} + + +OptionPtr +TestControl::factoryRequestList4(Option::Universe u, + uint16_t type, + const OptionBuffer& buf) { + const uint8_t buf_array[] = { + DHO_SUBNET_MASK, + DHO_BROADCAST_ADDRESS, + DHO_TIME_OFFSET, + DHO_ROUTERS, + DHO_DOMAIN_NAME, + DHO_DOMAIN_NAME_SERVERS, + DHO_HOST_NAME + }; + + OptionBuffer buf_with_options(buf_array, buf_array + sizeof(buf_array)); + OptionPtr opt(new Option(u, type, buf)); + opt->setData(buf_with_options.begin(), buf_with_options.end()); + return (opt); +} + +std::vector +TestControl::generateMacAddress(uint8_t& randomized) const { + CommandOptions& options = CommandOptions::instance(); + uint32_t clients_num = options.getClientsNum(); + if (clients_num < 2) { + return (options.getMacTemplate()); + } + // Get the base MAC address. We are going to randomize part of it. + std::vector mac_addr(options.getMacTemplate()); + if (mac_addr.size() != HW_ETHER_LEN) { + isc_throw(BadValue, "invalid MAC address template specified"); + } + uint32_t r = macaddr_gen_->generate(); + randomized = 0; + // Randomize MAC address octets. + for (std::vector::iterator it = mac_addr.end() - 1; + it >= mac_addr.begin(); + --it) { + // Add the random value to the current octet. + (*it) += r; + ++randomized; + if (r < 256) { + // If we are here it means that there is no sense + // to randomize the remaining octets of MAC address + // because the following bytes of random value + // are zero and it will have no effect. + break; + } + // Randomize the next octet with the following + // byte of random value. + r >>= 8; + } + return (mac_addr); +} + +std::vector +TestControl::generateDuid(uint8_t& randomized) const { + CommandOptions& options = CommandOptions::instance(); + uint32_t clients_num = options.getClientsNum(); + if ((clients_num == 0) || (clients_num == 1)) { + return (options.getDuidTemplate()); + } + // Get the base DUID. We are going to randomize part of it. + std::vector duid(options.getDuidTemplate()); + // @todo: add support for DUIDs of different sizes. + std::vector mac_addr(generateMacAddress(randomized)); + duid.resize(duid.size()); + std::copy(mac_addr.begin(), mac_addr.end(), + duid.begin() + duid.size() - mac_addr.size()); + return (duid); +} + +template +uint32_t +TestControl::getElapsedTime(const T& pkt1, const T& pkt2) { + using namespace boost::posix_time; + ptime pkt1_time = pkt1->getTimestamp(); + ptime pkt2_time = pkt2->getTimestamp(); + if (pkt1_time.is_not_a_date_time() || + pkt2_time.is_not_a_date_time()) { + isc_throw(InvalidOperation, "packet timestamp not set");; + } + time_period elapsed_period(pkt1_time, pkt2_time); + if (elapsed_period.is_null()) { + isc_throw(InvalidOperation, "unable to calculate time elapsed" + " between packets"); + } + return(elapsed_period.length().total_milliseconds()); +} + + +uint64_t +TestControl::getNextExchangesNum() const { + CommandOptions& options = CommandOptions::instance(); + // Reset number of exchanges. + uint64_t due_exchanges = 0; + // Get current time. + ptime now(microsec_clock::universal_time()); + if (now >= send_due_) { + // If rate is specified from the command line we have to + // synchornize with it. + if (options.getRate() != 0) { + time_period period(send_due_, now); + time_duration duration = period.length(); + // due_factor indicates the number of seconds that + // sending next chunk of packets will take. + double due_factor = duration.fractional_seconds() / + time_duration::ticks_per_second(); + due_factor += duration.total_seconds(); + // Multiplying due_factor by expected rate gives the number + // of exchanges to be initiated. + due_exchanges = static_cast(due_factor * options.getRate()); + // We want to make sure that at least one packet goes out. + if (due_exchanges == 0) { + due_exchanges = 1; + } + // We should not exceed aggressivity as it could have been + // restricted from command line. + if (due_exchanges > options.getAggressivity()) { + due_exchanges = options.getAggressivity(); + } + } else { + // Rate is not specified so we rely on aggressivity + // which is the number of packets to be sent in + // one chunk. + due_exchanges = options.getAggressivity(); + } + return (due_exchanges); + } + return (0); +} + +uint64_t +TestControl::getRcvdPacketsNum(const ExchangeType xchg_type) const { + uint8_t ip_version = CommandOptions::instance().getIpVersion(); + if (ip_version == 4) { + return (stats_mgr4_->getRcvdPacketsNum(xchg_type)); + } + return (stats_mgr6_-> + getRcvdPacketsNum(static_cast(xchg_type))); +} + +uint64_t +TestControl::getSentPacketsNum(const ExchangeType xchg_type) const { + uint8_t ip_version = CommandOptions::instance().getIpVersion(); + if (ip_version == 4) { + return (stats_mgr4_->getSentPacketsNum(xchg_type)); + } + return (stats_mgr6_-> + getSentPacketsNum(static_cast(xchg_type))); +} + +TestControl::TemplateBuffer +TestControl::getTemplateBuffer(const size_t idx) const { + if (template_buffers_.size() > idx) { + return (template_buffers_[idx]); + } + isc_throw(OutOfRange, "invalid buffer index"); +} + +void +TestControl::handleInterrupt(int) { + interrupted_ = true; +} + +void +TestControl::initPacketTemplates() { + template_buffers_.clear(); + CommandOptions& options = CommandOptions::instance(); + std::vector template_files = options.getTemplateFiles(); + for (std::vector::const_iterator it = template_files.begin(); + it != template_files.end(); ++it) { + readPacketTemplate(*it); + } +} + +void +TestControl::initializeStatsMgr() { + CommandOptions& options = CommandOptions::instance(); + if (options.getIpVersion() == 4) { + stats_mgr4_.reset(); + stats_mgr4_ = StatsMgr4Ptr(new StatsMgr4()); + stats_mgr4_->addExchangeStats(StatsMgr4::XCHG_DO); + if (options.getExchangeMode() == CommandOptions::DORA_SARR) { + stats_mgr4_->addExchangeStats(StatsMgr4::XCHG_RA); + } + + } else if (options.getIpVersion() == 6) { + stats_mgr6_.reset(); + stats_mgr6_ = StatsMgr6Ptr(new StatsMgr6()); + stats_mgr6_->addExchangeStats(StatsMgr6::XCHG_SA); + if (options.getExchangeMode() == CommandOptions::DORA_SARR) { + stats_mgr6_->addExchangeStats(StatsMgr6::XCHG_RR); + } + } + if (testDiags('i')) { + if (options.getIpVersion() == 4) { + stats_mgr4_->addCustomCounter("latesend", "Late sent packets"); + stats_mgr4_->addCustomCounter("shortwait", "Short waits for packets"); + stats_mgr4_->addCustomCounter("multircvd", "Multiple packets receives"); + // stats_mgr4_->addCustomCounter("latercvd", "Late received packets"); + } else if (options.getIpVersion() == 6) { + stats_mgr6_->addCustomCounter("latesend", "Late sent packets"); + stats_mgr6_->addCustomCounter("shortwait", "Short waits for packets"); + stats_mgr6_->addCustomCounter("multircvd", "Multiple packets receives"); + // stats_mgr6_->addCustomCounter("latercvd", "Late received packets"); + } + } +} + +int +TestControl::openSocket() const { + CommandOptions& options = CommandOptions::instance(); + std::string localname = options.getLocalName(); + std::string servername = options.getServerName(); + uint16_t port = options.getLocalPort(); + uint8_t family = AF_INET; + int sock = 0; + IOAddress remoteaddr(servername); + if (port == 0) { + if (options.getIpVersion() == 6) { + port = DHCP6_CLIENT_PORT; + } else if (options.getIpVersion() == 4) { + port = 67; // TODO: find out why port 68 is wrong here. + } + } + if (options.getIpVersion() == 6) { + family = AF_INET6; + } + // Local name is specified along with '-l' option. + // It may point to interface name or local address. + if (!localname.empty()) { + // CommandOptions should be already aware wether local name + // is interface name or address because it uses IfaceMgr to + // scan interfaces and get's their names. + if (options.isInterface()) { + sock = IfaceMgr::instance().openSocketFromIface(localname, + port, + family); + } else { + IOAddress localaddr(localname); + sock = IfaceMgr::instance().openSocketFromAddress(localaddr, + port); + } + } else if (!servername.empty()) { + // If only server name is given we will need to try to resolve + // the local address to bind socket to based on remote address. + sock = IfaceMgr::instance().openSocketFromRemoteAddress(remoteaddr, + port); + } + if (sock <= 0) { + isc_throw(BadValue, "unable to open socket to communicate with " + "DHCP server"); + } + + // IfaceMgr does not set broadcast option on the socket. We rely + // on CommandOptions object to find out if socket has to have + // broadcast enabled. + if ((options.getIpVersion() == 4) && options.isBroadcast()) { + int broadcast_enable = 1; + int ret = setsockopt(sock, SOL_SOCKET, SO_BROADCAST, + &broadcast_enable, sizeof(broadcast_enable)); + if (ret < 0) { + isc_throw(InvalidOperation, + "unable to set broadcast option on the socket"); + } + } else if (options.getIpVersion() == 6) { + // If remote address is multicast we need to enable it on + // the socket that has been created. + asio::ip::address_v6 remote_v6 = remoteaddr.getAddress().to_v6(); + if (remote_v6.is_multicast()) { + int hops = 1; + int ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, + &hops, sizeof(hops)); + // If user specified interface name with '-l' the + // IPV6_MULTICAST_IF has to be set. + if ((ret >= 0) && options.isInterface()) { + IfaceMgr::Iface* iface = + IfaceMgr::instance().getIface(options.getLocalName()); + if (iface == NULL) { + isc_throw(Unexpected, "unknown interface " + << options.getLocalName()); + } + int idx = iface->getIndex(); + ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, + &idx, sizeof(idx)); + } + if (ret < 0) { + isc_throw(InvalidOperation, + "unable to enable multicast on socket " << sock + << ". errno = " << errno); + } + } + } + + return (sock); +} + +void +TestControl::printDiagnostics() const { + CommandOptions& options = CommandOptions::instance(); + if (testDiags('a')) { + // Print all command line parameters. + options.printCommandLine(); + // Print MAC and DUID. + std::cout << "Set MAC to " << vector2Hex(options.getMacTemplate(), "::") + << std::endl; + if (options.getDuidTemplate().size() > 0) { + std::cout << "Set DUID to " << vector2Hex(options.getDuidTemplate()) << std::endl; + } + } +} + +void +TestControl::printRate() const { + double rate = 0; + CommandOptions& options = CommandOptions::instance(); + if (options.getIpVersion() == 4) { + double duration = + stats_mgr4_->getTestPeriod().length().total_nanoseconds() / 1e9; + rate = stats_mgr4_->getRcvdPacketsNum(StatsMgr4::XCHG_DO) / duration; + } else if (options.getIpVersion() == 6) { + double duration = + stats_mgr6_->getTestPeriod().length().total_nanoseconds() / 1e9; + rate = stats_mgr6_->getRcvdPacketsNum(StatsMgr6::XCHG_SA) / duration; + } + std::cout << "***Rate statistics***" << std::endl; + if (options.getRate() > 0) { + std::cout << "Rate: " << rate << ", expected rate: " + << options.getRate() << std::endl << std::endl; + } else { + std::cout << "Rate: " << rate << std::endl << std::endl; + } +} + +void +TestControl::printIntermediateStats() { + CommandOptions& options = CommandOptions::instance(); + int delay = options.getReportDelay(); + ptime now = microsec_clock::universal_time(); + time_period time_since_report(last_report_, now); + if (time_since_report.length().total_seconds() >= delay) { + if (options.getIpVersion() == 4) { + stats_mgr4_->printIntermediateStats(); + } else if (options.getIpVersion() == 6) { + stats_mgr6_->printIntermediateStats(); + } + last_report_ = now; + } +} + +void +TestControl::printStats() const { + printRate(); + CommandOptions& options = CommandOptions::instance(); + if (options.getIpVersion() == 4) { + if (!stats_mgr4_) { + isc_throw(InvalidOperation, "Statistics Manager for DHCPv4 " + "hasn't been initialized"); + } + stats_mgr4_->printStats(); + if (testDiags('i')) { + stats_mgr4_->printCustomCounters(); + } + } else if (options.getIpVersion() == 6) { + if (!stats_mgr6_) { + isc_throw(InvalidOperation, "Statistics Manager for DHCPv6 " + "hasn't been initialized"); + } + stats_mgr6_->printStats(); + if (testDiags('i')) { + stats_mgr6_->printCustomCounters(); + } + } +} + +std::string +TestControl::vector2Hex(const std::vector& vec, + const std::string& separator /* ="" */) const { + std::ostringstream stream; + for (std::vector::const_iterator it = vec.begin(); + it != vec.end(); + ++it) { + if (it == vec.begin()) { + stream << byte2Hex(*it); + } else { + stream << separator << byte2Hex(*it); + } + } + return (stream.str()); +} + +void +TestControl::readPacketTemplate(const std::string& file_name) { + std::ifstream temp_file; + temp_file.open(file_name.c_str(), ios::in | ios::binary | ios::ate); + if (!temp_file.is_open()) { + isc_throw(BadValue, "unable to open template file " << file_name); + } + std::ifstream::pos_type temp_size = temp_file.tellg(); + if (temp_size % 2 != 0) { + temp_file.close(); + isc_throw(BadValue, "odd number of digits in template file"); + } + temp_file.seekg(0, ios::beg); + std::vector hex_digits(temp_size); + std::vector binary_stream; + temp_file.read(&hex_digits[0], temp_size); + temp_file.close(); + for (int i = 0; i < hex_digits.size(); i += 2) { + if (!isxdigit(hex_digits[i]) || !isxdigit(hex_digits[i+1])) { + isc_throw(BadValue, "the '" << hex_digits[i] << hex_digits[i+1] + << "' is not hexadecimal digit"); + } + stringstream s; + s << "0x" << hex_digits[i] << hex_digits[i+1]; + int b; + s >> std::hex >> b; + binary_stream.push_back(static_cast(b)); + } + template_buffers_.push_back(binary_stream); +} + +void +TestControl::processReceivedPacket4(const TestControlSocket& socket, + const Pkt4Ptr& pkt4) { + if (pkt4->getType() == DHCPOFFER) { + Pkt4Ptr discover_pkt4(stats_mgr4_->passRcvdPacket(StatsMgr4::XCHG_DO, + pkt4)); + CommandOptions::ExchangeMode xchg_mode = + CommandOptions::instance().getExchangeMode(); + if ((xchg_mode == CommandOptions::DORA_SARR) && discover_pkt4) { + if (template_buffers_.size() < 2) { + sendRequest4(socket, discover_pkt4, pkt4); + } else { + // @todo add defines for packet type index that can be + // used to access template_buffers_. + sendRequest4(socket, template_buffers_[1], discover_pkt4, pkt4); + } + } + } else if (pkt4->getType() == DHCPACK) { + stats_mgr4_->passRcvdPacket(StatsMgr4::XCHG_RA, pkt4); + } +} + +void +TestControl::processReceivedPacket6(const TestControlSocket& socket, + const Pkt6Ptr& pkt6) { + uint8_t packet_type = pkt6->getType(); + if (packet_type == DHCPV6_ADVERTISE) { + Pkt6Ptr solicit_pkt6(stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_SA, + pkt6)); + CommandOptions::ExchangeMode xchg_mode = + CommandOptions::instance().getExchangeMode(); + if ((xchg_mode == CommandOptions::DORA_SARR) && solicit_pkt6) { + // \todo check whether received ADVERTISE packet is sane. + // We might want to check if STATUS_CODE option is non-zero + // and if there is IAADR option in IA_NA. + if (template_buffers_.size() < 2) { + sendRequest6(socket, pkt6); + } else { + // @todo add defines for packet type index that can be + // used to access template_buffers_. + sendRequest6(socket, template_buffers_[1], pkt6); + } + } + } else if (packet_type == DHCPV6_REPLY) { + stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RR, pkt6); + } +} + +void +TestControl::receivePackets(const TestControlSocket& socket) { + int timeout = 0; + bool receiving = true; + uint64_t received = 0; + while (receiving) { + if (CommandOptions::instance().getIpVersion() == 4) { + Pkt4Ptr pkt4 = IfaceMgr::instance().receive4(timeout); + if (!pkt4) { + receiving = false; + } else { + ++received; + if ((received > 1) && testDiags('i')) { + stats_mgr4_->incrementCounter("multircvd"); + } + pkt4->unpack(); + processReceivedPacket4(socket, pkt4); + } + } else if (CommandOptions::instance().getIpVersion() == 6) { + Pkt6Ptr pkt6 = IfaceMgr::instance().receive6(timeout); + if (!pkt6) { + receiving = false; + } else { + ++received; + if ((received > 1) && testDiags('i')) { + stats_mgr6_->incrementCounter("multircvd"); + } + if (pkt6->unpack()) { + processReceivedPacket6(socket, pkt6); + } + } + } + } +} + +void +TestControl::registerOptionFactories4() const { + static bool factories_registered = false; + if (!factories_registered) { + // DHCP_MESSAGE_TYPE option factory. + LibDHCP::OptionFactoryRegister(Option::V4, + DHO_DHCP_MESSAGE_TYPE, + &TestControl::factoryGeneric); + // DHCP_SERVER_IDENTIFIER option factory. + LibDHCP::OptionFactoryRegister(Option::V4, + DHO_DHCP_SERVER_IDENTIFIER, + &TestControl::factoryGeneric); + // DHCP_PARAMETER_REQUEST_LIST option factory. + LibDHCP::OptionFactoryRegister(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST, + &TestControl::factoryRequestList4); + } + factories_registered = true; +} + +void +TestControl::registerOptionFactories6() const { + static bool factories_registered = false; + if (!factories_registered) { + // D60_ELAPSED_TIME + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_ELAPSED_TIME, + &TestControl::factoryElapsedTime6); + // D6O_RAPID_COMMIT + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_RAPID_COMMIT, + &TestControl::factoryRapidCommit6); + // D6O_ORO (option request option) factory. + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_ORO, + &TestControl::factoryOptionRequestOption6); + // D6O_CLIENTID option factory. + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_CLIENTID, + &TestControl::factoryGeneric); + // D6O_SERVERID option factory. + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_SERVERID, + &TestControl::factoryGeneric); + // D6O_IA_NA option factory. + LibDHCP::OptionFactoryRegister(Option::V6, + D6O_IA_NA, + &TestControl::factoryIana6); + + + } + factories_registered = true; +} + +void +TestControl::registerOptionFactories() const { + CommandOptions& options = CommandOptions::instance(); + switch(options.getIpVersion()) { + case 4: + registerOptionFactories4(); + break; + case 6: + registerOptionFactories6(); + break; + default: + isc_throw(InvalidOperation, "command line options have to be parsed " + "before DHCP option factories can be registered"); + } +} + +void +TestControl::reset() { + send_due_ = microsec_clock::universal_time(); + last_sent_ = send_due_; + last_report_ = send_due_; + transid_gen_.reset(); + // Actual generators will have to be set later on because we need to + // get command line parameters first. + setTransidGenerator(NumberGeneratorPtr()); + setMacAddrGenerator(NumberGeneratorPtr()); + first_packet_serverid_.clear(); + interrupted_ = false; +} + +void +TestControl::run() { + // Reset singleton state before test starts. + reset(); + + CommandOptions& options = CommandOptions::instance(); + // Ip version is not set ONLY in case the command options + // were not parsed. This surely means that parse() function + // was not called prior to starting the test. This is fatal + // error. + if (options.getIpVersion() == 0) { + isc_throw(InvalidOperation, + "command options must be parsed before running a test"); + } else if (options.getIpVersion() == 4) { + setTransidGenerator(NumberGeneratorPtr(new SequencialGenerator())); + } else { + setTransidGenerator(NumberGeneratorPtr(new SequencialGenerator(0x00FFFFFF))); + } + + uint32_t clients_num = options.getClientsNum() == 0 ? + 1 : options.getClientsNum(); + setMacAddrGenerator(NumberGeneratorPtr(new SequencialGenerator(clients_num))); + + // Diagnostics are command line options mainly. + printDiagnostics(); + // Option factories have to be registered. + registerOptionFactories(); + TestControlSocket socket(openSocket()); + if (!socket.valid_) { + isc_throw(Unexpected, "invalid socket descriptor"); + } + // Initialize packet templates. + initPacketTemplates(); + // Initialize randomization seed. + if (options.isSeeded()) { + srandom(options.getSeed()); + } else { + // Seed with current time. + time_period duration(from_iso_string("20111231T235959"), + microsec_clock::universal_time()); + srandom(duration.length().total_seconds() + + duration.length().fractional_seconds()); + } + // If user interrupts the program we will exit gracefully. + signal(SIGINT, TestControl::handleInterrupt); + // Preload server with number of packets. + const bool do_preload = true; + for (int i = 0; i < options.getPreload(); ++i) { + if (options.getIpVersion() == 4) { + // No template buffer means no -T option specified. + // We will build packet ourselves. + if (template_buffers_.size() == 0) { + sendDiscover4(socket, do_preload); + } else { + // Pick template #0 if Discover is being sent. + // For Request it would be #1. + // @todo add defines for packet type index that can be + // used to access template_buffers_. + sendDiscover4(socket, template_buffers_[0], + do_preload); + } + } else if (options.getIpVersion() == 6) { + // No template buffer means no -T option specified. + // We will build packet ourselfs. + if (template_buffers_.size() == 0) { + sendSolicit6(socket, do_preload); + } else { + // Pick template #0 if Solicit is being sent. + // For Request it would be #1. + // @todo add defines for packet type index that can be + // used to access template_buffers_. + sendSolicit6(socket, template_buffers_[0], + do_preload); + } + } + } + // Initialize Statistics Manager. Release previous if any. + initializeStatsMgr(); + for (;;) { + // Calculate send due based on when last exchange was initiated. + updateSendDue(); + // If test period finished, maximum number of packet drops + // has been reached or test has been interrupted we have to + // finish the test. + if (checkExitConditions()) { + break; + } + // Calculate number of packets to be sent to stay + // catch up with rate. + uint64_t packets_due = getNextExchangesNum(); + if ((packets_due == 0) && testDiags('i')) { + if (options.getIpVersion() == 4) { + stats_mgr4_->incrementCounter("shortwait"); + } else if (options.getIpVersion() == 6) { + stats_mgr6_->incrementCounter("shortwait"); + } + } + + // @todo: set non-zero timeout for packets once we implement + // microseconds timeout in IfaceMgr. + receivePackets(socket); + // Send packets. + for (uint64_t i = packets_due; i > 0; --i) { + if (options.getIpVersion() == 4) { + // No template packets means that no -T option was specified. + // We have to build packets ourselfs. + if (template_buffers_.size() == 0) { + sendDiscover4(socket); + } else { + // @todo add defines for packet type index that can be + // used to access template_buffers_. + sendDiscover4(socket, template_buffers_[0]); + } + } else { + // No template packets means that no -T option was specified. + // We have to build packets ourselfs. + if (template_buffers_.size() == 0) { + sendSolicit6(socket); + } else { + // @todo add defines for packet type index that can be + // used to access template_buffers_. + sendSolicit6(socket, template_buffers_[0]); + } + } + } + // Report delay means that user requested printing number + // of sent/received/dropped packets repeatedly. + if (options.getReportDelay() > 0) { + printIntermediateStats(); + } + } + printStats(); + // Print server id. + if (testDiags('s') && (first_packet_serverid_.size() > 0)) { + std::cout << "Server id: " << vector2Hex(first_packet_serverid_) << std::endl; + } + // Diagnostics flag 'e' means show exit reason. + if (testDiags('e')) { + std::cout << "Interrupted" << std::endl; + } +} + +void +TestControl::sendDiscover4(const TestControlSocket& socket, + const bool preload /*= false*/) { + last_sent_ = microsec_clock::universal_time(); + // Generate the MAC address to be passed in the packet. + uint8_t randomized = 0; + std::vector mac_address = generateMacAddress(randomized); + // Generate trasnaction id to be set for the new exchange. + const uint32_t transid = generateTransid(); + Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, transid)); + if (!pkt4) { + isc_throw(Unexpected, "failed to create DISCOVER packet"); + } + // Set options: DHCP_MESSAGE_TYPE and DHCP_PARAMETER_REQUEST_LIST + OptionBuffer buf_msg_type; + buf_msg_type.push_back(DHCPDISCOVER); + pkt4->addOption(Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE, + buf_msg_type)); + pkt4->addOption(Option::factory(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST)); + + // Set client's and server's ports as well as server's address, + // and local (relay) address. + setDefaults4(socket, pkt4); + + // Set hardware address + pkt4->setHWAddr(HTYPE_ETHER, mac_address.size(), mac_address); + + pkt4->pack(); + IfaceMgr::instance().send(pkt4); + if (!preload) { + if (!stats_mgr4_) { + isc_throw(InvalidOperation, "Statistics Manager for DHCPv4 " + "hasn't been initialized"); + } + stats_mgr4_->passSentPacket(StatsMgr4::XCHG_DO, pkt4); + } +} + +void +TestControl::sendDiscover4(const TestControlSocket& socket, + const std::vector& template_buf, + const bool preload /* = false */) { + // last_sent_ has to be updated for each function that initiates + // new transaction. The packet exchange synchronization relies on this. + last_sent_ = microsec_clock::universal_time(); + CommandOptions& options = CommandOptions::instance(); + // Get the first argument if mulitple the same arguments specified + // in the command line. First one refers to DISCOVER packets. + const uint8_t arg_idx = 0; + // Generate the MAC address to be passed in the packet. + uint8_t randomized = 0; + std::vector mac_address = generateMacAddress(randomized); + // Generate trasnaction id to be set for the new exchange. + const uint32_t transid = generateTransid(); + // Get transaction id offset. + size_t transid_offset = DHCPV4_TRANSID_OFFSET; + if (options.getTransactionIdOffset().size() > arg_idx) { + transid_offset = options.getTransactionIdOffset()[arg_idx]; + } + // Calculate randomization offset. + size_t rand_offset = DHCPV4_RANDOMIZATION_OFFSET; + if (options.getRandomOffset().size() > arg_idx) { + rand_offset = options.getRandomOffset()[arg_idx]; + } + // We need to go back by HW_ETHER_LEN (MAC address length) + // because this offset points to last octet of MAC address. + rand_offset -= HW_ETHER_LEN + 1; + // Create temporary buffer with template contents. We will + // modify this temporary buffer but we don't want to modify + // the original template. + std::vector in_buf(template_buf.begin(), + template_buf.end()); + // Check if we are not going out of bounds. + if (rand_offset + HW_ETHER_LEN > in_buf.size()) { + isc_throw(OutOfRange, "randomization offset is out of bounds"); + } + PerfPkt4Ptr pkt4(new PerfPkt4(&in_buf[0], in_buf.size(), + transid_offset, + transid)); + + // Replace MAC address in the template with actual MAC address. + pkt4->writeAt(rand_offset, mac_address.begin(), mac_address.end()); + // Create a packet from the temporary buffer. + setDefaults4(socket, boost::static_pointer_cast(pkt4)); + // Pack the input packet buffer to output buffer so as it can + // be sent to server. + pkt4->rawPack(); + IfaceMgr::instance().send(boost::static_pointer_cast(pkt4)); + if (!preload) { + if (!stats_mgr4_) { + isc_throw(InvalidOperation, "Statistics Manager for DHCPv4 " + "hasn't been initialized"); + } + // Update packet stats. + stats_mgr4_->passSentPacket(StatsMgr4::XCHG_DO, + boost::static_pointer_cast(pkt4)); + } +} + +void +TestControl::sendRequest4(const TestControlSocket& socket, + const dhcp::Pkt4Ptr& discover_pkt4, + const dhcp::Pkt4Ptr& offer_pkt4) { + const uint32_t transid = generateTransid(); + Pkt4Ptr pkt4(new Pkt4(DHCPREQUEST, transid)); + OptionBuffer buf_msg_type; + buf_msg_type.push_back(DHCPREQUEST); + OptionPtr opt_msg_type = Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE, + buf_msg_type); + pkt4->addOption(opt_msg_type); + if (CommandOptions::instance().isUseFirst() && + (first_packet_serverid_.size() > 0)) { + pkt4->addOption(Option::factory(Option::V4, DHO_DHCP_SERVER_IDENTIFIER, + first_packet_serverid_)); + } else { + OptionPtr opt_serverid = + offer_pkt4->getOption(DHO_DHCP_SERVER_IDENTIFIER); + if (!opt_serverid) { + isc_throw(BadValue, "there is no SERVER_IDENTIFIER option " + << "in OFFER message"); + } + if (stats_mgr4_->getRcvdPacketsNum(StatsMgr4::XCHG_DO) == 1) { + first_packet_serverid_ = opt_serverid->getData(); + } + pkt4->addOption(opt_serverid); + } + + /// Set client address. + asiolink::IOAddress yiaddr = offer_pkt4->getYiaddr(); + if (!yiaddr.getAddress().is_v4()) { + isc_throw(BadValue, "the YIADDR returned in OFFER packet is not " + " IPv4 address"); + } + OptionPtr opt_requested_address = + OptionPtr(new Option(Option::V4, DHO_DHCP_REQUESTED_ADDRESS, + OptionBuffer())); + opt_requested_address->setUint32(static_cast(yiaddr)); + pkt4->addOption(opt_requested_address); + OptionPtr opt_parameter_list = + Option::factory(Option::V4, DHO_DHCP_PARAMETER_REQUEST_LIST); + pkt4->addOption(opt_parameter_list); + // Set client's and server's ports as well as server's address, + // and local (relay) address. + setDefaults4(socket, pkt4); + + // Set hardware address + const uint8_t* chaddr = offer_pkt4->getChaddr(); + std::vector mac_address(chaddr, chaddr + HW_ETHER_LEN); + pkt4->setHWAddr(HTYPE_ETHER, mac_address.size(), mac_address); + // Set elapsed time. + uint32_t elapsed_time = getElapsedTime(discover_pkt4, offer_pkt4); + pkt4->setSecs(static_cast(elapsed_time / 1000)); + // Prepare on wire data to send. + pkt4->pack(); + IfaceMgr::instance().send(pkt4); + if (!stats_mgr4_) { + isc_throw(InvalidOperation, "Statistics Manager for DHCPv4 " + "hasn't been initialized"); + } + stats_mgr4_->passSentPacket(StatsMgr4::XCHG_RA, pkt4); +} + +void +TestControl::sendRequest4(const TestControlSocket& socket, + const std::vector& template_buf, + const dhcp::Pkt4Ptr& discover_pkt4, + const dhcp::Pkt4Ptr& offer_pkt4) { + CommandOptions& options = CommandOptions::instance(); + // Get the second argument if multiple the same arguments specified + // in the command line. Second one refers to REQUEST packets. + const uint8_t arg_idx = 1; + // Generate new transaction id. + const uint32_t transid = generateTransid(); + // Get transaction id offset. + size_t transid_offset = DHCPV4_TRANSID_OFFSET; + if (options.getTransactionIdOffset().size() > arg_idx) { + transid_offset = options.getTransactionIdOffset()[arg_idx]; + } + // Get the offset of MAC's last octet. + size_t rand_offset = DHCPV4_RANDOMIZATION_OFFSET; + if (options.getRandomOffset().size() > arg_idx) { + rand_offset = options.getRandomOffset()[arg_idx]; + } + // We need to go back by HW_ETHER_LEN (MAC address length) + // because this offset points to last octet of MAC address. + rand_offset -= HW_ETHER_LEN + 1; + // Create temporaru buffer from the template. + std::vector in_buf(template_buf.begin(), + template_buf.end()); + // Check if given randomization offset is not out of bounds. + if (rand_offset + HW_ETHER_LEN > in_buf.size()) { + isc_throw(OutOfRange, "randomization offset is out of bounds"); + } + + // Create packet from the temporary buffer. + PerfPkt4Ptr pkt4(new PerfPkt4(&in_buf[0], in_buf.size(), + transid_offset, + transid)); + + // Set hardware address from OFFER packet received. + const uint8_t* chaddr = offer_pkt4->getChaddr(); + std::vector mac_address(chaddr, chaddr + HW_ETHER_LEN); + pkt4->writeAt(rand_offset, mac_address.begin(), mac_address.end()); + + // Set elapsed time. + size_t elp_offset = 0; + if (options.getElapsedTimeOffset() > 0) { + elp_offset = options.getElapsedTimeOffset(); + } + uint32_t elapsed_time = getElapsedTime(discover_pkt4, offer_pkt4); + pkt4->writeValueAt(elp_offset, + static_cast(elapsed_time / 1000)); + + // Get the actual server id offset. + size_t sid_offset = DHCPV4_SERVERID_OFFSET; + if (options.getServerIdOffset() > 0) { + sid_offset = options.getServerIdOffset(); + } + if (CommandOptions::instance().isUseFirst() && + (first_packet_serverid_.size() > 0)) { + boost::shared_ptr + opt_serverid(new LocalizedOption(Option::V4, + DHO_DHCP_SERVER_IDENTIFIER, + first_packet_serverid_, + sid_offset)); + pkt4->addOption(opt_serverid); + } else { + // Copy the contents of server identifier received in + // OFFER packet to put this into REQUEST. + OptionPtr opt_serverid_offer = + offer_pkt4->getOption(DHO_DHCP_SERVER_IDENTIFIER); + if (!opt_serverid_offer) { + isc_throw(BadValue, "there is no SERVER_IDENTIFIER option " + << "in OFFER message"); + } + boost::shared_ptr + opt_serverid(new LocalizedOption(Option::V4, + DHO_DHCP_SERVER_IDENTIFIER, + opt_serverid_offer->getData(), + sid_offset)); + pkt4->addOption(opt_serverid); + if (stats_mgr4_->getRcvdPacketsNum(StatsMgr4::XCHG_DO) == 1) { + first_packet_serverid_ = opt_serverid_offer->getData(); + } + } + + /// Set client address. + asiolink::IOAddress yiaddr = offer_pkt4->getYiaddr(); + if (!yiaddr.getAddress().is_v4()) { + isc_throw(BadValue, "the YIADDR returned in OFFER packet is not " + " IPv4 address"); + } + + // Get the actual offset of requested ip. + size_t rip_offset = DHCPV4_REQUESTED_IP_OFFSET; + if (options.getRequestedIpOffset() > 0) { + rip_offset = options.getRequestedIpOffset(); + } + // Place requested IP option at specified position (rip_offset). + boost::shared_ptr + opt_requested_ip(new LocalizedOption(Option::V4, + DHO_DHCP_REQUESTED_ADDRESS, + OptionBuffer(), + rip_offset)); + // The IOAddress is castable to uint32_t and returns exactly what we need. + opt_requested_ip->setUint32(static_cast(yiaddr)); + pkt4->addOption(opt_requested_ip); + + setDefaults4(socket, boost::static_pointer_cast(pkt4)); + // Prepare on-wire data. + pkt4->rawPack(); + IfaceMgr::instance().send(boost::static_pointer_cast(pkt4)); + if (!stats_mgr4_) { + isc_throw(InvalidOperation, "Statistics Manager for DHCPv4 " + "hasn't been initialized"); + } + // Update packet stats. + stats_mgr4_->passSentPacket(StatsMgr4::XCHG_RA, + boost::static_pointer_cast(pkt4)); +} + +void +TestControl::sendRequest6(const TestControlSocket& socket, + const Pkt6Ptr& advertise_pkt6) { + const uint32_t transid = generateTransid(); + Pkt6Ptr pkt6(new Pkt6(DHCPV6_REQUEST, transid)); + // Set elapsed time. + OptionPtr opt_elapsed_time = + Option::factory(Option::V6, D6O_ELAPSED_TIME); + pkt6->addOption(opt_elapsed_time); + // Set client id. + OptionPtr opt_clientid = advertise_pkt6->getOption(D6O_CLIENTID); + if (!opt_clientid) { + isc_throw(Unexpected, "client id not found in received packet"); + } + pkt6->addOption(opt_clientid); + + // Use first flags indicates that we want to use the server + // id captured in fisrt packet. + if (CommandOptions::instance().isUseFirst() && + (first_packet_serverid_.size() > 0)) { + pkt6->addOption(Option::factory(Option::V6, D6O_SERVERID, + first_packet_serverid_)); + } else { + OptionPtr opt_serverid = advertise_pkt6->getOption(D6O_SERVERID); + if (!opt_serverid) { + isc_throw(Unexpected, "server id not found in received packet"); + } + if (stats_mgr6_->getRcvdPacketsNum(StatsMgr6::XCHG_SA) == 1) { + first_packet_serverid_ = opt_serverid->getData(); + } + pkt6->addOption(opt_serverid); + } + // Set IA_NA option. + OptionPtr opt_ia_na = advertise_pkt6->getOption(D6O_IA_NA); + if (!opt_ia_na) { + isc_throw(Unexpected, "DHCPv6 IA_NA option not found in received " + "packet"); + } + pkt6->addOption(opt_ia_na); + + // Set default packet data. + setDefaults6(socket, pkt6); + // Prepare on-wire data. + pkt6->pack(); + IfaceMgr::instance().send(pkt6); + if (!stats_mgr6_) { + isc_throw(InvalidOperation, "Statistics Manager for DHCPv6 " + "hasn't been initialized"); + } + stats_mgr6_->passSentPacket(StatsMgr6::XCHG_RR, pkt6); +} + +void +TestControl::sendRequest6(const TestControlSocket& socket, + const std::vector& template_buf, + const Pkt6Ptr& advertise_pkt6) { + CommandOptions& options = CommandOptions::instance(); + // Get the second argument if multiple the same arguments specified + // in the command line. Second one refers to REQUEST packets. + const uint8_t arg_idx = 1; + // Generate transaction id. + const uint32_t transid = generateTransid(); + // Get transaction id offset. + size_t transid_offset = DHCPV6_TRANSID_OFFSET; + if (options.getTransactionIdOffset().size() > arg_idx) { + transid_offset = options.getTransactionIdOffset()[arg_idx]; + } + PerfPkt6Ptr pkt6(new PerfPkt6(&template_buf[0], template_buf.size(), + transid_offset, transid)); + // Set elapsed time. + size_t elp_offset = DHCPV6_ELAPSED_TIME_OFFSET; + if (options.getElapsedTimeOffset() > 0) { + elp_offset = options.getElapsedTimeOffset(); + } + boost::shared_ptr + opt_elapsed_time(new LocalizedOption(Option::V6, D6O_ELAPSED_TIME, + OptionBuffer(), elp_offset)); + pkt6->addOption(opt_elapsed_time); + + // Get the actual server id offset. + size_t sid_offset = DHCPV6_SERVERID_OFFSET; + if (options.getServerIdOffset() > 0) { + sid_offset = options.getServerIdOffset(); + } + if (CommandOptions::instance().isUseFirst() && + (first_packet_serverid_.size() > 0)) { + boost::shared_ptr + opt_serverid(new LocalizedOption(Option::V6, + D6O_SERVERID, + first_packet_serverid_, + sid_offset)); + pkt6->addOption(opt_serverid); + + } else { + // Copy the contents of server identifier received in + // ADVERTISE packet to put this into REQUEST. + OptionPtr opt_serverid_advertise = + advertise_pkt6->getOption(D6O_SERVERID); + if (!opt_serverid_advertise) { + isc_throw(BadValue, "there is no SERVERID option " + << "in ADVERTISE message"); + } + boost::shared_ptr + opt_serverid(new LocalizedOption(Option::V6, + D6O_SERVERID, + opt_serverid_advertise->getData(), + sid_offset)); + pkt6->addOption(opt_serverid); + if (stats_mgr6_->getRcvdPacketsNum(StatsMgr6::XCHG_SA) == 1) { + first_packet_serverid_ = opt_serverid_advertise->getData(); + } + } + // Set IA_NA + boost::shared_ptr opt_ia_na_advertise = + boost::static_pointer_cast(advertise_pkt6->getOption(D6O_IA_NA)); + if (!opt_ia_na_advertise) { + isc_throw(Unexpected, "DHCPv6 IA_NA option not found in received " + "packet"); + } + size_t addr_offset = DHCPV6_IA_NA_OFFSET; + if (options.getRequestedIpOffset() > 0) { + addr_offset = options.getRequestedIpOffset(); + } + boost::shared_ptr + opt_ia_na(new LocalizedOption(opt_ia_na_advertise, addr_offset)); + if (!opt_ia_na->valid()) { + isc_throw(BadValue, "Option IA_NA in advertise packet is invalid"); + } + pkt6->addOption(opt_ia_na); + // Set server id. + OptionPtr opt_serverid_advertise = advertise_pkt6->getOption(D6O_SERVERID); + if (!opt_serverid_advertise) { + isc_throw(Unexpected, "DHCPV6 SERVERID option not found in received " + "packet"); + } + size_t srvid_offset = DHCPV6_SERVERID_OFFSET; + if (options.getServerIdOffset() > 0) { + srvid_offset = options.getServerIdOffset(); + } + boost::shared_ptr + opt_serverid(new LocalizedOption(Option::V6, D6O_SERVERID, + opt_serverid_advertise->getData(), + srvid_offset)); + pkt6->addOption(opt_serverid); + // Get randomization offset. + size_t rand_offset = DHCPV6_RANDOMIZATION_OFFSET; + if (options.getRandomOffset().size() > arg_idx) { + rand_offset = options.getRandomOffset()[arg_idx]; + } + OptionPtr opt_clientid_advertise = advertise_pkt6->getOption(D6O_CLIENTID); + if (!opt_clientid_advertise) { + isc_throw(Unexpected, "DHCPV6 CLIENTID option not found in received packet"); + } + rand_offset -= (opt_clientid_advertise->len() - 1); + // Set client id. + boost::shared_ptr + opt_clientid(new LocalizedOption(Option::V6, D6O_CLIENTID, + opt_clientid_advertise->getData(), + rand_offset)); + pkt6->addOption(opt_clientid); + // Set default packet data. + setDefaults6(socket, pkt6); + // Prepare on wire data. + pkt6->rawPack(); + // Send packet. + IfaceMgr::instance().send(pkt6); + if (!stats_mgr6_) { + isc_throw(InvalidOperation, "Statistics Manager for DHCPv6 " + "hasn't been initialized"); + } + // Update packet stats. + stats_mgr6_->passSentPacket(StatsMgr6::XCHG_RR, pkt6); + +} + +void +TestControl::sendSolicit6(const TestControlSocket& socket, + const bool preload /*= false*/) { + last_sent_ = microsec_clock::universal_time(); + // Generate DUID to be passed to the packet + uint8_t randomized = 0; + std::vector duid = generateDuid(randomized); + // Generate trasnaction id to be set for the new exchange. + const uint32_t transid = generateTransid(); + Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, transid)); + if (!pkt6) { + isc_throw(Unexpected, "failed to create SOLICIT packet"); + } + pkt6->addOption(Option::factory(Option::V6, D6O_ELAPSED_TIME)); + if (CommandOptions::instance().isRapidCommit()) { + pkt6->addOption(Option::factory(Option::V6, D6O_RAPID_COMMIT)); + } + pkt6->addOption(Option::factory(Option::V6, D6O_CLIENTID, duid)); + pkt6->addOption(Option::factory(Option::V6, D6O_ORO)); + pkt6->addOption(Option::factory(Option::V6, D6O_IA_NA)); + + setDefaults6(socket, pkt6); + pkt6->pack(); + IfaceMgr::instance().send(pkt6); + if (!preload) { + if (!stats_mgr6_) { + isc_throw(InvalidOperation, "Statistics Manager for DHCPv6 " + "hasn't been initialized"); + } + stats_mgr6_->passSentPacket(StatsMgr6::XCHG_SA, pkt6); + } +} + +void +TestControl::sendSolicit6(const TestControlSocket& socket, + const std::vector& template_buf, + const bool preload /*= false*/) { + last_sent_ = microsec_clock::universal_time(); + CommandOptions& options = CommandOptions::instance(); + const int arg_idx = 0; + // Get transaction id offset. + size_t transid_offset = DHCPV6_TRANSID_OFFSET; + if (options.getTransactionIdOffset().size() > arg_idx) { + transid_offset = options.getTransactionIdOffset()[arg_idx]; + } + // Generate trasnaction id to be set for the new exchange. + const uint32_t transid = generateTransid(); + // Create packet. + PerfPkt6Ptr pkt6(new PerfPkt6(&template_buf[0], template_buf.size(), + transid_offset, transid)); + if (!pkt6) { + isc_throw(Unexpected, "failed to create SOLICIT packet"); + } + size_t rand_offset = DHCPV6_RANDOMIZATION_OFFSET; + if (options.getRandomOffset().size() > arg_idx) { + rand_offset = options.getRandomOffset()[arg_idx]; + } + // randomized will pick number of bytes randomized so we can + // just use part of the generated duid and substitude a few bytes + /// in template. + uint8_t randomized = 0; + std::vector duid = generateDuid(randomized); + if (rand_offset > template_buf.size()) { + isc_throw(OutOfRange, "randomization offset is out of bounds"); + } + // Store random part of the DUID into the packet. + pkt6->writeAt(rand_offset - randomized + 1, + duid.end() - randomized, duid.end()); + + // Prepare on-wire data. + pkt6->rawPack(); + setDefaults6(socket, pkt6); + // Send solicit packet. + IfaceMgr::instance().send(pkt6); + if (!preload) { + if (!stats_mgr6_) { + isc_throw(InvalidOperation, "Statistics Manager for DHCPv6 " + "hasn't been initialized"); + } + // Update packet stats. + stats_mgr6_->passSentPacket(StatsMgr6::XCHG_SA, pkt6); + } +} + + +void +TestControl::setDefaults4(const TestControlSocket& socket, + const Pkt4Ptr& pkt) { + CommandOptions& options = CommandOptions::instance(); + // Interface name. + IfaceMgr::Iface* iface = IfaceMgr::instance().getIface(socket.ifindex_); + if (iface == NULL) { + isc_throw(BadValue, "unable to find interface with given index"); + } + pkt->setIface(iface->getName()); + // Interface index. + pkt->setIndex(socket.ifindex_); + // Local client's port (68) + pkt->setLocalPort(DHCP4_CLIENT_PORT); + // Server's port (67) + pkt->setRemotePort(DHCP4_SERVER_PORT); + // The remote server's name or IP. + pkt->setRemoteAddr(IOAddress(options.getServerName())); + // Set local addresss. + pkt->setLocalAddr(IOAddress(socket.addr_)); + // Set relay (GIADDR) address to local address. + pkt->setGiaddr(IOAddress(socket.addr_)); + // Pretend that we have one relay (which is us). + pkt->setHops(1); +} + +void +TestControl::setDefaults6(const TestControlSocket& socket, + const Pkt6Ptr& pkt) { + CommandOptions& options = CommandOptions::instance(); + // Interface name. + IfaceMgr::Iface* iface = IfaceMgr::instance().getIface(socket.ifindex_); + if (iface == NULL) { + isc_throw(BadValue, "unable to find interface with given index"); + } + pkt->setIface(iface->getName()); + // Interface index. + pkt->setIndex(socket.ifindex_); + // Local client's port (547) + pkt->setLocalPort(DHCP6_CLIENT_PORT); + // Server's port (548) + pkt->setRemotePort(DHCP6_SERVER_PORT); + // Set local address. + pkt->setLocalAddr(socket.addr_); + // The remote server's name or IP. + pkt->setRemoteAddr(IOAddress(options.getServerName())); +} + +bool +TestControl::testDiags(const char diag) const { + std::string diags(CommandOptions::instance().getDiags()); + if (diags.find(diag) != std::string::npos) { + return (true); + } + return (false); +} + +void +TestControl::updateSendDue() { + // If default constructor was called, this should not happen but + // if somebody has changed default constructor it is better to + // keep this check. + if (last_sent_.is_not_a_date_time()) { + isc_throw(Unexpected, "time of last sent packet not initialized"); + } + // Get the expected exchange rate. + CommandOptions& options = CommandOptions::instance(); + int rate = options.getRate(); + // If rate was not specified we will wait just one clock tick to + // send next packet. This simulates best effort conditions. + long duration = 1; + if (rate != 0) { + // We use number of ticks instead of nanoseconds because + // nanosecond resolution may not be available on some + // machines. Number of ticks guarantees the highest possible + // timer resolution. + duration = time_duration::ticks_per_second() / rate; + } + // Calculate due time to initate next chunk of exchanges. + send_due_ = last_sent_ + time_duration(0, 0, 0, duration); + // Check if it is already due. + ptime now(microsec_clock::universal_time()); + // \todo verify if this condition is not too tight. In other words + // verify if this will not produce too many late sends. + // We might want to look at this once we are done implementing + // microsecond timeouts in IfaceMgr. + if (now > send_due_) { + if (testDiags('i')) { + if (options.getIpVersion() == 4) { + stats_mgr4_->incrementCounter("latesend"); + } else if (options.getIpVersion() == 6) { + stats_mgr6_->incrementCounter("latesend"); + } + } + } +} + + +} // namespace perfdhcp +} // namespace isc diff --git a/tests/tools/perfdhcp/test_control.h b/tests/tools/perfdhcp/test_control.h new file mode 100644 index 0000000000..1b0b83c385 --- /dev/null +++ b/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 +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 StatsMgr4; + /// Pointer to Statistics Manager for DHCPv4; + typedef boost::shared_ptr StatsMgr4Ptr; + /// Statictics Manager for DHCPv6. + typedef StatsMgr StatsMgr6; + /// Pointer to Statistics Manager for DHCPv6. + typedef boost::shared_ptr StatsMgr6Ptr; + /// Packet exchange type. + typedef StatsMgr<>::ExchangeType ExchangeType; + /// Packet template buffer. + typedef std::vector TemplateBuffer; + /// Packet template buffers list. + typedef std::vector 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 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 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 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& 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& 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& 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& 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 + 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& 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 diff --git a/tests/tools/perfdhcp/tests/Makefile.am b/tests/tools/perfdhcp/tests/Makefile.am index 16e18425ac..73ec6babc5 100644 --- a/tests/tools/perfdhcp/tests/Makefile.am +++ b/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) diff --git a/tests/tools/perfdhcp/tests/command_options_helper.h b/tests/tools/perfdhcp/tests/command_options_helper.h new file mode 100644 index 0000000000..860a040fd3 --- /dev/null +++ b/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 +#include + +#include +#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 text_iterator(text_stream); + std::istream_iterator text_end; + // Tokenize string (space is a separator) using begin and end iteratos + std::vector tokens(text_iterator, text_end); + + if (tokens.size() > 0) { + // Allocate array of C-strings where we will store tokens + results = static_cast(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(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 diff --git a/tests/tools/perfdhcp/tests/command_options_unittest.cc b/tests/tools/perfdhcp/tests/command_options_unittest.cc index 8e1053dc30..0481d28da3 100644 --- a/tests/tools/perfdhcp/tests/command_options_unittest.cc +++ b/tests/tools/perfdhcp/tests/command_options_unittest.cc @@ -16,14 +16,17 @@ #include #include #include +#include + +#include +#include #include "../command_options.h" -#include "exceptions/exceptions.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 v1 = opt.getMacPrefix(); + const uint8_t mac[6] = { 0x00, 0x0C, 0x01, 0x02, 0x03, 0x04 }; + std::vector 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 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 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 v1 = opt.getMacTemplate(); + std::vector 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 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 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 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 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()); } diff --git a/tests/tools/perfdhcp/tests/perf_pkt4_unittest.cc b/tests/tools/perfdhcp/tests/perf_pkt4_unittest.cc index 3863faa111..5523c640ec 100644 --- a/tests/tools/perfdhcp/tests/perf_pkt4_unittest.cc +++ b/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 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(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(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(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(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]); +} + } diff --git a/tests/tools/perfdhcp/tests/stats_mgr_unittest.cc b/tests/tools/perfdhcp/tests/stats_mgr_unittest.cc index 2233847bb1..d6b3aeff24 100644 --- a/tests/tools/perfdhcp/tests/stats_mgr_unittest.cc +++ b/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. diff --git a/tests/tools/perfdhcp/tests/test_control_unittest.cc b/tests/tools/perfdhcp/tests/test_control_unittest.cc new file mode 100644 index 0000000000..a4cde00f19 --- /dev/null +++ b/tests/tools/perfdhcp/tests/test_control_unittest.cc @@ -0,0 +1,1011 @@ +// 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 +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include "command_options_helper.h" +#include "../test_control.h" + +using namespace std; +using namespace boost::posix_time; +using namespace isc; +using namespace isc::dhcp; +using namespace isc::perfdhcp; + +/// \brief Test Control class with protected members made public. +/// +/// This class makes protected TestControl class'es member public +/// to allow unit testing. +class NakedTestControl: public TestControl { +public: + + /// \brief Incremental transaction id generaator. + /// + /// This is incremental transaction id generator. It overrides + /// the default transaction id generator that generates transaction + /// ids using random function. This generator will generate values + /// like: 1,2,3 etc. + class IncrementalGenerator : public TestControl::NumberGenerator { + public: + /// \brief Default constructor. + IncrementalGenerator() : + NumberGenerator(), + transid_(0) { + } + + /// \brief Generate unique transaction id. + /// + /// Generate unique transaction ids incrementally: + /// 1,2,3,4 etc. + /// + /// \return generated transaction id. + virtual uint32_t generate() { + return (++transid_); + } + private: + uint32_t transid_; ///< Last generated transaction id. + }; + + using TestControl::checkExitConditions; + using TestControl::factoryElapsedTime6; + using TestControl::factoryGeneric; + using TestControl::factoryIana6; + using TestControl::factoryOptionRequestOption6; + using TestControl::factoryRapidCommit6; + using TestControl::factoryRequestList4; + using TestControl::generateDuid; + using TestControl::generateMacAddress; + using TestControl::getNextExchangesNum; + using TestControl::getTemplateBuffer; + using TestControl::initPacketTemplates; + using TestControl::initializeStatsMgr; + using TestControl::openSocket; + using TestControl::processReceivedPacket4; + using TestControl::processReceivedPacket6; + using TestControl::registerOptionFactories; + using TestControl::sendDiscover4; + using TestControl::sendSolicit6; + using TestControl::setDefaults4; + using TestControl::setDefaults6; + + NakedTestControl() : TestControl() { + uint32_t clients_num = CommandOptions::instance().getClientsNum() == 0 ? + 1 : CommandOptions::instance().getClientsNum(); + setMacAddrGenerator(NumberGeneratorPtr(new TestControl::SequencialGenerator(clients_num))); + }; + +}; + +/// \brief Test Fixture Class +/// +/// This test fixture class is used to perform +/// unit tests on perfdhcp TestControl class. +class TestControlTest : public virtual ::testing::Test +{ +public: + + typedef std::vector MacAddress; + typedef MacAddress::iterator MacAddressIterator; + + typedef std::vector Duid; + typedef Duid::iterator DuidIterator; + + /// \brief Default Constructor + TestControlTest() { } + + /// \brief Create packet template file from binary data. + /// + /// Function creates file containing data from the provided buffer + /// in hexadecimal format. + /// \param filename template file to be created. + /// \param buffer with binary datato be stored in file. + /// \return true if file creation successful. + bool createTemplateFile(const std::string& filename, + const std::vector& buf) const { + std::ofstream temp_file; + temp_file.open(filename.c_str(), ios::out | ios::trunc); + if (!temp_file.is_open()) { + return (false); + } + for (int i = 0; i < buf.size(); ++i) { + int first_digit = buf[i] / 16; + int second_digit = buf[i] % 16; + temp_file << std::hex << first_digit << second_digit << std::dec; + } + temp_file.close(); + return (true); + } + + /// \brief Get local loopback interface name. + /// + /// Scan available network interfaces for local loopback + /// interface and get its name. On Linux this interface is + /// usually called 'lo' but on other systems, e.g. BSD + /// it will have slightly different name. Local loopback + /// interface is required for unit tests that require + /// socket creation. + /// + /// \return local loopback interface name. + std::string getLocalLoopback() const { + const IfaceMgr::IfaceCollection& ifaces = + IfaceMgr::instance().getIfaces(); + for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin(); + iface != ifaces.end(); + ++iface) { + if (iface->flag_loopback_) { + return (iface->getName()); + } + } + return (""); + } + + /// \brief Match requested options in the buffer with given list. + /// + /// This method iterates through options provided in the buffer + /// and matches them with the options specified with first parameter. + /// Options in both vectors may be laid in different order. + /// + /// \param requested_options reference buffer with options. + /// \param buf test buffer with options that will be matched. + /// \return number of options from the buffer matched with options + /// in the reference buffer. + int matchRequestedOptions(const dhcp::OptionBuffer& requested_options, + const dhcp::OptionBuffer& buf) const { + size_t matched_num = 0; + for (size_t i = 0; i < buf.size(); ++i) { + for (int j = 0; j < requested_options.size(); ++j) { + if (requested_options[j] == buf[i]) { + // Requested option has been found. + ++matched_num; + } + } + } + return (matched_num); + } + + /// \brief Match requested DHCPv6 options in the buffer with given list. + /// + /// This method iterates through options provided in the buffer and + /// matches them with the options specified with first parameter. + /// Options in both vectors ma be laid in different order. + /// + /// \param requested_options reference buffer with options. + /// \param buf test buffer with options that will be matched. + /// \return number of options from the buffer matched with options in + /// the reference buffer or -1 if error occured. + int matchRequestedOptions6(const dhcp::OptionBuffer& requested_options, + const dhcp::OptionBuffer& buf) const { + // Sanity check. + if ((requested_options.size() % 2 != 0) || + (buf.size() % 2 != 0)) { + return -1; + } + size_t matched_num = 0; + for (size_t i = 0; i < buf.size(); i += 2) { + for (int j = 0; j < requested_options.size(); j += 2) { + uint16_t opt_i = buf[i + 1] << 8 + buf[i] & 0xFF; + uint16_t opt_j = requested_options[j + 1] << 8 + requested_options[j] & 0xFF; + if (opt_i == opt_j) { + // Requested option has been found. + ++matched_num; + } + } + } + return (matched_num); + } + + /// \brief Calculate the maximum vectors' mismatch position. + /// + /// This helper function calculates the maximum mismatch position + /// between two vectors (two different DUIDs or MAC addresses). + /// Calculated position is counted from the end of vectors. + /// Calculation is based on number of simulated clients. When number + /// of clients is less than 256 different DUIDs or MAC addresses can + /// can be coded in such a way that they differ on last vector element. + /// If number of clients is between 257 and 65536 they can differ + /// on two last positions so the returned value will be 2 and so on. + /// + /// \param clients_num number of simulated clinets + /// \return maximum mismatch position + int unequalOctetPosition(int clients_num) const { + if (!clients_num) { + return (0); + } + clients_num--; + + int cnt = 0; + while (clients_num) { + clients_num >>= 8; + ++cnt; + } + + return (cnt); + } + + /// brief Test generation of mulitple DUIDs + /// + /// Thie method checks the generation of multiple DUIDs. Number + /// of iterations depends on the number of simulated clients. + /// It is expected that DUID's size is 14 (consists of DUID-LLT + /// HW type field, 4 octets of time value and MAC address). The + /// MAC address can be randomized depending on the number of + /// simulated clients. The DUID-LLT and HW type are expected to + /// be constant. The time value has to be properly calculated + /// as the number of seconds since DUID time epoch. The parts + /// of MAC address has to change if multiple clients are simulated + /// and do not change if single client is simulated. + void testDuid() const { + int clients_num = CommandOptions::instance().getClientsNum(); + // Initialize Test Control class. + NakedTestControl tc; + // The old duid will be holding the previously generated DUID. + // It will be used to compare against the new one. If we have + // multiple clients we want to make sure that duids differ. + uint8_t randomized = 0; + Duid old_duid(tc.generateDuid(randomized)); + Duid new_duid(0); + // total_dist shows the total difference between generated duid. + // It has to be greater than zero if multiple clients are simulated. + size_t total_dist = 0; + // Number of unique DUIDs. + size_t unique_duids = 0; + // Holds the position if the octet on which two DUIDS can be different. + // If number of clients is 256 or less it is last DUID octet (except for + // single client when subsequent DUIDs have to be equal). If number of + // clients is between 257 and 65536 the last two octets can differ etc. + int unequal_pos = unequalOctetPosition(clients_num); + // Keep generated DUIDs in this container. + std::list > duids; + // Perform number of iterations to generate number of DUIDs. + for (int i = 0; i < 10 * clients_num; ++i) { + if (new_duid.empty()) { + new_duid = old_duid; + } else { + std::swap(old_duid, new_duid); + new_duid = tc.generateDuid(randomized); + } + // 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; + ASSERT_EQ(duid_llt_size, new_duid.size()); + // The first four octets do not change. + EXPECT_TRUE(std::equal(new_duid.begin(), new_duid.begin() + 4, + duid_llt_and_hw)); + + // 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.' + uint32_t duid_time = 0; + // Pick 4 bytes of the time from generated DUID and put them + // in reverse order (in DUID they are stored in network order). + for (int j = 4; j < 8; ++j) { + duid_time |= new_duid[j] << (j - 4); + } + // Calculate the duration since epoch time. + ptime now = microsec_clock::universal_time(); + ptime duid_epoch(from_iso_string("20000101T000000")); + time_period period(duid_epoch, now); + + // Current time is the same or later than time from the DUID because + // DUID had been generated before reference duration was calculated. + EXPECT_GE(period.length().total_seconds(), duid_time); + + // Get the mismatch position (counting from the end) of + // mismatched octet between previously generated DUID + // and current. + std::pair mismatch_pos = + std::mismatch(old_duid.begin(), old_duid.end(), + new_duid.begin()); + size_t mismatch_dist = + std::distance(mismatch_pos.first, old_duid.end()); + // For single client total_dist is expected to be 0 because + // old_duid and new_duid should always match. If we have + // more clients then duids have to differ except the case + // if randomization algorithm generates the same values but + // this would be an error in randomization algorithm. + total_dist += mismatch_dist; + // Mismatch may have occured on the DUID octet position + // up to calculated earlier unequal_pos. + ASSERT_LE(mismatch_dist, unequal_pos); + // unique will inform if tested DUID is unique. + bool unique = true; + for (std::list >::const_iterator it = + duids.begin(); + it != duids.end(); ++it) { + // DUIDs should be of the same size if we want to compare them. + ASSERT_EQ(new_duid.size(), it->size()); + // Check if DUID is unique. + if (std::equal(new_duid.begin(), new_duid.end(), it->begin())) { + unique = false; + } + } + // Expecting that DUIDs will be unique only when + // first clients-num iterations is performed. + // After that, DUIDs become non unique. + if (unique) { + ++unique_duids; + } + // For number of iterations equal to clients_num,2*clients_num + // 3*clients_num ... we have to have number of unique duids + // equal to clients_num. + if ((i != 0) && (i % clients_num == 0)) { + ASSERT_EQ(clients_num, unique_duids); + } + // Remember generated DUID. + duids.push_back(new_duid); + } + // If we have more than one client at least one mismatch occured. + if (clients_num < 2) { + EXPECT_EQ(0, total_dist); + } + } + + /// \brief Test DHCPv4 exchanges. + /// + /// Function simulates DHCPv4 exchanges. Function caller specifies + /// number of exchanges to be simulated and number of simulated + /// responses. When number of responses is lower than number of + /// iterations than the difference between them is the number + /// of simulated packet drops. This is useful to test if program + /// exit conditions are handled properly (maximum number of packet + /// drops specified as -D is taken into account). + /// + /// \param iterations_num number of exchanges to simulate. + /// \param receive_num number of received OFFER packets. + /// \param iterations_performed actual number of iterations. + void testPkt4Exchange(int iterations_num, + int receive_num, + bool use_templates, + int& iterations_performed) const { + int sock_handle = 0; + NakedTestControl tc; + tc.initializeStatsMgr(); + + // Use templates files to crate packets. + if (use_templates) { + tc.initPacketTemplates(); + ASSERT_NO_THROW(tc.getTemplateBuffer(0)); + ASSERT_NO_THROW(tc.getTemplateBuffer(1)); + } + + // Incremental transaction id generator will generate + // predictable values of transaction id for each iteration. + // This is important because we need to simulate responses + // from the server and use the same transaction ids as in + // packets sent by client. + TestControl::NumberGeneratorPtr + generator(new NakedTestControl::IncrementalGenerator()); + tc.setTransidGenerator(generator); + // Socket is needed to send packets through the interface. + ASSERT_NO_THROW(sock_handle = tc.openSocket()); + TestControl::TestControlSocket sock(sock_handle); + uint32_t transid = 0; + for (int i = 0; i < iterations_num; ++i) { + if (use_templates) { + ASSERT_NO_THROW(tc.sendDiscover4(sock, tc.getTemplateBuffer(0))); + } else { + ASSERT_NO_THROW(tc.sendDiscover4(sock)); + } + ++transid; + // Do not simulate responses for packets later + // that specified as receive_num. This simulates + // packet drops. + if (i < receive_num) { + boost::shared_ptr offer_pkt4(createOfferPkt4(transid)); + ASSERT_NO_THROW(tc.processReceivedPacket4(sock, offer_pkt4)); + ++transid; + } + if (tc.checkExitConditions()) { + iterations_performed = i + 1; + break; + } + iterations_performed = i + 1; + } + } + + /// \brief Test DHCPv6 exchanges. + /// + /// Function simulates DHCPv6 exchanges. Function caller specifies + /// number of exchanges to be simulated and number of simulated + /// responses. When number of responses is lower than number of + /// iterations than the difference between them is the number + /// of simulated packet drops. This is useful to test if program + /// exit conditions are handled properly (maximum number of packet + /// drops specified as -D is taken into account). + /// + /// \param iterations_num number of exchanges to simulate. + /// \param receive_num number of received OFFER packets. + /// \param iterations_performed actual number of iterations. + void testPkt6Exchange(int iterations_num, + int receive_num, + bool use_templates, + int& iterations_performed) const { + int sock_handle = 0; + NakedTestControl tc; + tc.initializeStatsMgr(); + + // Use templates files to crate packets. + if (use_templates) { + tc.initPacketTemplates(); + ASSERT_NO_THROW(tc.getTemplateBuffer(0)); + ASSERT_NO_THROW(tc.getTemplateBuffer(1)); + } + + // Incremental transaction id generator will generate + // predictable values of transaction id for each iteration. + // This is important because we need to simulate reponses + // from the server and use the same transaction ids as in + // packets sent by client. + TestControl::NumberGeneratorPtr + generator(new NakedTestControl::IncrementalGenerator()); + tc.setTransidGenerator(generator); + // Socket is needed to send packets through the interface. + ASSERT_NO_THROW(sock_handle = tc.openSocket()); + TestControl::TestControlSocket sock(sock_handle); + uint32_t transid = 0; + for (int i = 0; i < iterations_num; ++i) { + // Do not simulate responses for packets later + // that specified as receive_num. This simulates + // packet drops. + if (use_templates) { + ASSERT_NO_THROW(tc.sendSolicit6(sock, tc.getTemplateBuffer(0))); + } else { + ASSERT_NO_THROW(tc.sendSolicit6(sock)); + } + ++transid; + if (i < receive_num) { + boost::shared_ptr + advertise_pkt6(createAdvertisePkt6(transid)); + // Receive ADVERTISE and send REQUEST. + ASSERT_NO_THROW(tc.processReceivedPacket6(sock, advertise_pkt6)); + ++transid; + } + if (tc.checkExitConditions()) { + iterations_performed = i + 1; + break; + } + iterations_performed = i + 1; + } + } + + /// \brief Test generation of multiple MAC addresses. + /// + /// This method validates generation of multiple MAC addresses. + /// The MAC address can be randomized depending on the number + /// of simulated clients. This test checks if different MAC + /// addresses are generated if number of simulated clients is + /// greater than 1. It also checks if the same MAC addresses is + /// generated if only 1 client is simulated. + void testMacAddress() const { + int clients_num = CommandOptions::instance().getClientsNum(); + // The old_mac will be holding the value of previously generated + // MAC address. We will be comparing the newly generated one with it + // to see if it changes when mulitple clients are simulated or if it + // does not change when single client is simulated. + MacAddress old_mac(CommandOptions::instance().getMacTemplate()); + // Holds the position if the octet on which two MAC addresses can + // be different. If number of clients is 256 or less it is last MAC + // octet (except for single client when subsequent MAC addresses + // have to be equal). If number of clients is between 257 and 65536 + // the last two octets can differ etc. + int unequal_pos = unequalOctetPosition(clients_num); + // Number of unique MACs. + size_t unique_macs = 0; + // Initialize Test Controller. + NakedTestControl tc; + size_t total_dist = 0; + // Keep generated MACs in this container. + std::list > macs; + // Do many iterations to generate and test MAC address values. + for (int i = 0; i < clients_num * 10; ++i) { + // Generate new MAC address. + uint8_t randomized = 0; + MacAddress new_mac(tc.generateMacAddress(randomized)); + // Get the mismatch position (counting from the end) of + // mismatched octet between previously generated MAC address + // and current. + std::pair mismatch_pos = + std::mismatch(old_mac.begin(), old_mac.end(), new_mac.begin()); + size_t mismatch_dist = + std::distance(mismatch_pos.first, old_mac.end()); + // For single client total_dist is expected to be 0 because + // old_mac and new_mac should always match. If we have + // more clients then MAC addresses have to differ except + // the case if randomization algorithm generates the same + // values but this would be an error in randomization algorithm. + total_dist += mismatch_dist; + // Mismatch may have occured on the MAC address'es octet position + // up to calculated earlier unequal_pos. + ASSERT_LE(mismatch_dist, unequal_pos); + // unique will inform if tested DUID is unique. + bool unique = true; + for (std::list >::const_iterator it = + macs.begin(); + it != macs.end(); ++it) { + // MACs should be of the same size if we want to compare them. + ASSERT_EQ(new_mac.size(), it->size()); + // Check if MAC is unique. + if (std::equal(new_mac.begin(), new_mac.end(), it->begin())) { + unique = false; + } + } + // Expecting that MACs will be unique only when + // first clients-num iterations is performed. + // After that, MACs become non unique. + if (unique) { + ++unique_macs; + } + // For number of iterations equal to clients_num,2*clients_num + // 3*clients_num ... we have to have number of unique MACs + // equal to clients_num. + if ((i != 0) && (i % clients_num == 0)) { + ASSERT_EQ(clients_num, unique_macs); + } + // Remember generated MAC. + macs.push_back(new_mac); + + } + if (clients_num < 2) { + EXPECT_EQ(total_dist, 0); + } + } + + /// \brief Parse command line string with CommandOptions. + /// + /// \param cmdline command line string to be parsed. + /// \throw isc::Unexpected if unexpected error occured. + /// \throw isc::InvalidParameter if command line is invalid. + void processCmdLine(const std::string& cmdline) const { + CommandOptionsHelper::process(cmdline); + } + +private: + /// \brief Create DHCPv4 OFFER packet. + /// + /// \param transid transaction id. + /// \return instance of the packet. + boost::shared_ptr + createOfferPkt4(uint32_t transid) const { + boost::shared_ptr offer(new Pkt4(DHCPOFFER, transid)); + OptionPtr opt_msg_type = Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE, + OptionBuffer(DHCPOFFER)); + OptionPtr opt_serverid = Option::factory(Option::V4, + DHO_DHCP_SERVER_IDENTIFIER, + OptionBuffer(4, 1)); + offer->setYiaddr(asiolink::IOAddress("127.0.0.1")); + offer->addOption(opt_msg_type); + offer->addOption(opt_serverid); + offer->updateTimestamp(); + return (offer); + } + + /// \brief Create DHCPv6 ADVERTISE packet. + /// + /// \param transid transaction id. + /// \return instance of the packet. + boost::shared_ptr + createAdvertisePkt6(uint32_t transid) const { + OptionPtr opt_ia_na = Option::factory(Option::V6, D6O_IA_NA); + OptionPtr opt_serverid(new Option(Option::V6, D6O_SERVERID)); + NakedTestControl tc; + uint8_t randomized = 0; + std::vector duid(tc.generateDuid(randomized)); + OptionPtr opt_clientid(Option::factory(Option::V6, D6O_CLIENTID, duid)); + boost::shared_ptr advertise(new Pkt6(DHCPV6_ADVERTISE, transid)); + advertise->addOption(opt_ia_na); + advertise->addOption(opt_serverid); + advertise->addOption(opt_clientid); + advertise->updateTimestamp(); + return (advertise); + } + +}; + +TEST_F(TestControlTest, GenerateDuid) { + // Simple command line that simulates one client only. Always the + // same DUID will be generated. + ASSERT_NO_THROW(processCmdLine("perfdhcp -l 127.0.0.1 all")); + testDuid(); + + // Simulate 50 clients. Different DUID will be generated. + ASSERT_NO_THROW(processCmdLine("perfdhcp -l 127.0.0.1 -R 50 all")); + testDuid(); +} + +TEST_F(TestControlTest, GenerateMacAddress) { + // Simulate one client only. Always the same MAC address will be + // generated. + ASSERT_NO_THROW(processCmdLine("perfdhcp -l 127.0.0.1 all")); + testMacAddress(); + + // Simulate 50 clients. Different MAC addresses will be generated. + ASSERT_NO_THROW(processCmdLine("perfdhcp -l 127.0.0.1 -R 50 all")); + testMacAddress(); +} + +TEST_F(TestControlTest, Options4) { + using namespace isc::dhcp; + NakedTestControl tc; + // By default the IP version mode is V4 so there is no need to + // parse command line to override the IP version. Note that + // registerOptionFactories is used for both V4 and V6. + tc.registerOptionFactories(); + // Create option with buffer size equal to 1 and holding DHCPDISCOVER + // message type. + OptionPtr opt_msg_type(Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE, + OptionBuffer(1, DHCPDISCOVER))); + // Validate the option type and universe. + EXPECT_EQ(Option::V4, opt_msg_type->getUniverse()); + EXPECT_EQ(DHO_DHCP_MESSAGE_TYPE, opt_msg_type->getType()); + // Validate the message type from the option we have now created. + uint8_t msg_type = 0; + ASSERT_NO_THROW(msg_type = opt_msg_type->getUint8()); + EXPECT_EQ(DHCPDISCOVER, msg_type); + + // Create another option: DHCP_PARAMETER_REQUEST_LIST + OptionPtr + opt_requested_options(Option::factory(Option::V4, + DHO_DHCP_PARAMETER_REQUEST_LIST)); + // Here is a list of options that we are requesting in the + // server's response. + const uint8_t requested_options[] = { + DHO_SUBNET_MASK, + DHO_BROADCAST_ADDRESS, + DHO_TIME_OFFSET, + DHO_ROUTERS, + DHO_DOMAIN_NAME, + DHO_DOMAIN_NAME_SERVERS, + DHO_HOST_NAME + }; + + OptionBuffer + requested_options_ref(requested_options, + requested_options + sizeof(requested_options)); + + // Get the option buffer. It should hold the combination of values + // listed in requested_options array. However their order can be + // different in general so we need to search each value separatelly. + const OptionBuffer& requested_options_buf = + opt_requested_options->getData(); + EXPECT_EQ(requested_options_ref.size(), requested_options_buf.size()); + size_t matched_num = matchRequestedOptions(requested_options_ref, + requested_options_buf); + // We want exactly the same requested options as listed in + // requested_options array - nothing more or less. + EXPECT_EQ(requested_options_ref.size(), matched_num); +} + +TEST_F(TestControlTest, Options6) { + using namespace isc::dhcp; + + // Lets override the IP version to test V6 options (-6 parameter) + ASSERT_NO_THROW(processCmdLine("perfdhcp -l 127.0.0.1 -6 all")); + + NakedTestControl tc; + tc.registerOptionFactories(); + + // Validate the D6O_ELAPSED_TIME option. + OptionPtr opt_elapsed_time(Option::factory(Option::V6, D6O_ELAPSED_TIME)); + // Validate the option type and universe. + EXPECT_EQ(Option::V6, opt_elapsed_time->getUniverse()); + EXPECT_EQ(D6O_ELAPSED_TIME, opt_elapsed_time->getType()); + // The default value of elapsed time is zero. + uint16_t elapsed_time; + ASSERT_NO_THROW(elapsed_time = opt_elapsed_time->getUint16()); + EXPECT_EQ(0, elapsed_time); + + // With the factory function we may also specify the actual + // value of elapsed time. Let's make use of std::vector + // constructor to create the option buffer, 2 octets long + // with each octet initialized to 0x1. + size_t elapsed_time_buf_size = 2; + uint8_t elapsed_time_pattern = 0x1; + OptionPtr + opt_elapsed_time2(Option::factory(Option::V6, D6O_ELAPSED_TIME, + OptionBuffer(elapsed_time_buf_size, + elapsed_time_pattern))); + + // Any buffer that has size neither equal to 0 nor 2 is considered invalid. + elapsed_time_buf_size = 1; + EXPECT_THROW( + Option::factory(Option::V6, D6O_ELAPSED_TIME, + OptionBuffer(elapsed_time_buf_size, elapsed_time_pattern)), + isc::BadValue + ); + + // Validate the option type and universe. + EXPECT_EQ(Option::V6, opt_elapsed_time2->getUniverse()); + EXPECT_EQ(D6O_ELAPSED_TIME, opt_elapsed_time2->getType()); + // Make sure the getUint16 does not throw exception. It wile throw + // buffer is shorter than 2 octets. + ASSERT_NO_THROW(elapsed_time = opt_elapsed_time2->getUint16()); + // Check the expected value of elapsed time. + EXPECT_EQ(0x0101, elapsed_time); + + // Validate the D6O_RAPID_COMMIT option. + OptionPtr opt_rapid_commit(Option::factory(Option::V6, D6O_RAPID_COMMIT)); + // Validate the option type and universe. + EXPECT_EQ(Option::V6, opt_rapid_commit->getUniverse()); + EXPECT_EQ(D6O_RAPID_COMMIT, opt_rapid_commit->getType()); + // Rapid commit has no data payload. + EXPECT_THROW(opt_rapid_commit->getUint8(), isc::OutOfRange); + + // Validate the D6O_CLIENTID option. + OptionBuffer duid(CommandOptions::instance().getDuidTemplate()); + OptionPtr opt_clientid(Option::factory(Option::V6, D6O_CLIENTID, duid)); + EXPECT_EQ(Option::V6, opt_clientid->getUniverse()); + EXPECT_EQ(D6O_CLIENTID, opt_clientid->getType()); + const OptionBuffer& duid2 = opt_clientid->getData(); + ASSERT_EQ(duid.size(), duid2.size()); + // The Duid we set for option is the same we get. + EXPECT_TRUE(std::equal(duid.begin(), duid.end(), duid2.begin())); + + // Validate the D6O_ORO (Option Request Option). + OptionPtr opt_oro(Option::factory(Option::V6, D6O_ORO)); + // Prepare the reference buffer with requested options. + const uint8_t requested_options[] = { + 0, D6O_NAME_SERVERS, + 0, D6O_DOMAIN_SEARCH, + }; + int requested_options_num = + sizeof(requested_options) / sizeof(requested_options[0]); + OptionBuffer + requested_options_ref(requested_options, + requested_options + sizeof(requested_options)); + // Get the buffer from option. + const OptionBuffer& requested_options_buf = opt_oro->getData(); + // Size of reference buffer and option buffer have to be + // the same for comparison. + EXPECT_EQ(requested_options_ref.size(), requested_options_buf.size()); + // Check if all options in the buffer are matched with reference buffer. + size_t matched_num = matchRequestedOptions6(requested_options_ref, + requested_options_buf); + EXPECT_EQ(requested_options_num, matched_num); + + // Validate the D6O_IA_NA option. + OptionPtr opt_ia_na(Option::factory(Option::V6, D6O_IA_NA)); + EXPECT_EQ(Option::V6, opt_ia_na->getUniverse()); + EXPECT_EQ(D6O_IA_NA, opt_ia_na->getType()); + // Every IA_NA option is expected to start with this sequence. + const uint8_t opt_ia_na_array[] = { + 0, 0, 0, 1, // IAID = 1 + 0, 0, 3600 >> 8, 3600 & 0xff, // T1 = 3600 + 0, 0, 5400 >> 8, 5400 & 0xff, // T2 = 5400 + }; + OptionBuffer opt_ia_na_ref(opt_ia_na_array, + opt_ia_na_array + sizeof(opt_ia_na_array)); + const OptionBuffer& opt_ia_na_buf = opt_ia_na->getData(); + ASSERT_EQ(opt_ia_na_buf.size(), opt_ia_na_ref.size()); + EXPECT_TRUE(std::equal(opt_ia_na_ref.begin(), opt_ia_na_ref.end(), + opt_ia_na_buf.begin())); + + // @todo Add more tests for IA address options. +} + +TEST_F(TestControlTest, Packet4) { + // Use Interface Manager to get the local loopback interface. + // If interface can't be found we don't want to fail test. + std::string loopback_iface(getLocalLoopback()); + if (!loopback_iface.empty()) { + ASSERT_NO_THROW(processCmdLine("perfdhcp -l " + loopback_iface + + " -L 10547 all")); + NakedTestControl tc; + int sock_handle = 0; + // We have to create the socket to setup some parameters of + // outgoing packet. + ASSERT_NO_THROW(sock_handle = tc.openSocket()); + TestControl::TestControlSocket sock(sock_handle); + uint32_t transid = 123; + boost::shared_ptr pkt4(new Pkt4(DHCPDISCOVER, transid)); + // Set parameters on outgoing packet. + ASSERT_NO_THROW(tc.setDefaults4(sock, pkt4)); + // Validate that packet has been setup correctly. + EXPECT_EQ(loopback_iface, pkt4->getIface()); + EXPECT_EQ(sock.ifindex_, pkt4->getIndex()); + EXPECT_EQ(DHCP4_CLIENT_PORT, pkt4->getLocalPort()); + EXPECT_EQ(DHCP4_SERVER_PORT, pkt4->getRemotePort()); + EXPECT_EQ(1, pkt4->getHops()); + EXPECT_EQ(asiolink::IOAddress("255.255.255.255"), + pkt4->getRemoteAddr()); + EXPECT_EQ(asiolink::IOAddress(sock.addr_), pkt4->getLocalAddr()); + EXPECT_EQ(asiolink::IOAddress(sock.addr_), pkt4->getGiaddr()); + } else { + std::cout << "Unable to find the loopback interface. Skip test. " + << std::endl; + } +} + +TEST_F(TestControlTest, Packet6) { + // Use Interface Manager to get the local loopback interface. + // If the interface can't be found we don't want to fail test. + std::string loopback_iface(getLocalLoopback()); + if (!loopback_iface.empty()) { + ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l " + loopback_iface + + " -L 10547 servers")); + NakedTestControl tc; + int sock_handle = 0; + // Create the socket. It will be needed to set packet's + // parameters. + ASSERT_NO_THROW(sock_handle = tc.openSocket()); + TestControl::TestControlSocket sock(sock_handle); + uint32_t transid = 123; + boost::shared_ptr pkt6(new Pkt6(DHCPV6_SOLICIT, transid)); + // Set packet's parameters. + ASSERT_NO_THROW(tc.setDefaults6(sock, pkt6)); + // Validate if parameters have been set correctly. + EXPECT_EQ(loopback_iface, pkt6->getIface()); + EXPECT_EQ(sock.ifindex_, pkt6->getIndex()); + EXPECT_EQ(DHCP6_CLIENT_PORT, pkt6->getLocalPort()); + EXPECT_EQ(DHCP6_SERVER_PORT, pkt6->getRemotePort()); + EXPECT_EQ(sock.addr_, pkt6->getLocalAddr()); + EXPECT_EQ(asiolink::IOAddress("FF05::1:3"), pkt6->getRemoteAddr()); + } else { + std::cout << "Unable to find the loopback interface. Skip test. " + << std::endl; + } +} + +TEST_F(TestControlTest, Packet4Exchange) { + // Get the local loopback interface to open socket on + // it and test packets exchanges. We don't want to fail + // the test if interface is not available. + std::string loopback_iface(getLocalLoopback()); + if (loopback_iface.empty()) { + std::cout << "Unable to find the loopback interface. Skip test." + << std::endl; + return; + } + + // Set number of iterations to some high value. + const int iterations_num = 100; + processCmdLine("perfdhcp -l " + loopback_iface + + " -r 100 -n 10 -R 20 -L 10547 127.0.0.1"); + // The actual number of iterations will be stored in the + // following variable. + int iterations_performed = 0; + bool use_templates = false; + testPkt4Exchange(iterations_num, iterations_num, use_templates, iterations_performed); + // The command line restricts the number of iterations to 10 + // with -n 10 parameter. + EXPECT_EQ(10, iterations_performed); + + // With the following command line we restrict the maximum + // number of dropped packets to 20% of all. + // Use templates for this test. + processCmdLine("perfdhcp -l " + loopback_iface + + " -r 100 -R 20 -n 20 -D 10% -L 10547" + + " -T ../templates/discover-example.hex" + + " -T ../templates/request4-example.hex" + + " 127.0.0.1"); + // The number iterations is restricted by the percentage of + // dropped packets (-D 10%). We also have to bump up the number + // of iterations because the percentage limitation checks starts + // at packet #10. We expect that at packet #12 the 10% threshold + // will be reached. + const int received_num = 10; + use_templates = true; + testPkt4Exchange(iterations_num, received_num, use_templates, iterations_performed); + EXPECT_EQ(12, iterations_performed); +} + +TEST_F(TestControlTest, Packet6Exchange) { + // Get the local loopback interface to open socket on + // it and test packets exchanges. We don't want to fail + // the test if interface is not available. + std::string loopback_iface(getLocalLoopback()); + if (loopback_iface.empty()) { + std::cout << "Unable to find the loopback interface. Skip test." + << std::endl; + return; + } + + const int iterations_num = 100; + // Set number of iterations to 10. + processCmdLine("perfdhcp -l " + loopback_iface + + " -6 -r 100 -n 10 -R 20 -L 10547 ::1"); + int iterations_performed = 0; + // Set number of received packets equal to number of iterations. + // This simulates no packet drops. + bool use_templates = false; + testPkt6Exchange(iterations_num, iterations_num, use_templates, + iterations_performed); + // Actual number of iterations should be 10. + EXPECT_EQ(10, iterations_performed); + + // The maximum number of dropped packets is 3 (because of -D 3). + use_templates = true; + processCmdLine("perfdhcp -l " + loopback_iface + + " -6 -r 100 -n 10 -R 20 -D 3 -L 10547" + + " -T ../templates/solicit-example.hex" + + " -T ../templates/request6-example.hex ::1"); + // For the first 3 packets we are simulating responses from server. + // For other packets we don't so packet as 4,5,6 will be dropped and + // then test should be interrupted and actual number of iterations will + // be 6. + const int received_num = 3; + testPkt6Exchange(iterations_num, received_num, use_templates, + iterations_performed); + EXPECT_EQ(6, iterations_performed); +} + +TEST_F(TestControlTest, PacketTemplates) { + std::vector template1(256); + std::string file1("../templates/test1.hex"); + std::vector template2(233); + std::string file2("../templates/test2.hex"); + for (int i = 0; i < template1.size(); ++i) { + template1[i] = static_cast(random() % 256); + } + for (int i = 0; i < template2.size(); ++i) { + template2[i] = static_cast(random() % 256); + } + ASSERT_TRUE(createTemplateFile(file1, template1)); + ASSERT_TRUE(createTemplateFile(file2, template2)); + CommandOptions& options = CommandOptions::instance(); + NakedTestControl tc; + + ASSERT_NO_THROW( + processCmdLine("perfdhcp -l 127.0.0.1" + " -T " + file1 + " -T " + file2 + " all") + ); + ASSERT_NO_THROW(tc.initPacketTemplates()); + TestControl::TemplateBuffer buf1; + TestControl::TemplateBuffer buf2; + ASSERT_NO_THROW(buf1 = tc.getTemplateBuffer(0)); + ASSERT_NO_THROW(buf2 = tc.getTemplateBuffer(1)); + ASSERT_EQ(template1.size(), buf1.size()); + ASSERT_EQ(template2.size(), buf2.size()); + EXPECT_TRUE(std::equal(template1.begin(), template1.end(), buf1.begin())); + EXPECT_TRUE(std::equal(template2.begin(), template2.end(), buf2.begin())); +} + +TEST_F(TestControlTest, RateControl) { + // We don't specify the exchange rate here so the aggressivity + // value will determine how many packets are to be send each + // time we query the getNextExchangesNum. + ASSERT_NO_THROW(processCmdLine("perfdhcp -l 127.0.0.1 all")); + CommandOptions& options = CommandOptions::instance(); + + NakedTestControl tc1; + uint64_t xchgs_num = tc1.getNextExchangesNum(); + EXPECT_EQ(options.getAggressivity(), xchgs_num); + + // The exchange rate is now 1 per second. We don't know how many + // exchanges have to initiated exactly but for sure it has to be + // non-zero value. Also, since aggressivity is very high we expect + // that it will not be restricted by aggressivity. + ASSERT_NO_THROW( + processCmdLine("perfdhcp -l 127.0.0.1 -a 1000000 -r 1 all") + ); + NakedTestControl tc2; + xchgs_num = tc2.getNextExchangesNum(); + EXPECT_GT(xchgs_num, 0); + EXPECT_LT(xchgs_num, options.getAggressivity()); + // @todo add more thorough checks for rate values. +}