BIND 10 master, updated. a8cf043db8f44604c7773e047a9dc2861e58462a Merge branch 'trac1959'
BIND 10 source code commits
bind10-changes at lists.isc.org
Wed Sep 12 14:33:14 UTC 2012
The branch, master has been updated
via a8cf043db8f44604c7773e047a9dc2861e58462a (commit)
via 9a3a5db5ee5cf4571a2c573e046faef0be07410f (commit)
via 9ddbc590eab2267c8c7b98f1c41f5d75d81a860c (commit)
via 45ca30e42d6f87bd1039cbcd7f83dfa15ebdcfa0 (commit)
via 1a4c5a70d4917bd1942d5f940bfaf0a1e35b6a0f (commit)
via 1b84c5e9b4391869185ceb41195b462f553b4f25 (commit)
via b5aa1dadff2c0e7c9fce8fab0aa6dee28071e29b (commit)
via 10cf1a64da6a0d8851984e6ca0c6c7baa6769c20 (commit)
via 58131685191d678f16320f532d86ddc2a90211ce (commit)
via f6ef8ec398f1711245c25eeb30887fe4de18641f (commit)
via fe103d1a3845a08dbf3d0af2bb3c17f10f78a6d7 (commit)
via 9f8954c665a65233a6c697be3769f02d702a2728 (commit)
via 7c7d7c9120c5a8a8b7b9d324ee6733a968412750 (commit)
via 5585299f3e340017f57307bbf9568bb98931279f (commit)
via c2fa97e23503ea00f82e0adff38d14473f475da8 (commit)
via ad297b10887895eae9cb6e3c5e00e38f7d1f5fda (commit)
via 6a3c2c618081b259a7eef9f747b1e6cc8aea4572 (commit)
via 6ca3dadca8255b8bb2aa58421f3f4289efe4e63a (commit)
via 6efd035f6178974adb48ace599f6d2335c0afd7e (commit)
via c47f7da793ecc7bb0486d6f78b9405803c2d2701 (commit)
via b753e8ebd1f6b3e16628e22640efab38fff2cac8 (commit)
via 23c33b29ab5841a3fe5469d2056708e707938998 (commit)
via 61f144876b5c42de12b51fc79ea2f6b0544001ce (commit)
via 3e6ed8d54322d7ebeb2721664dd7d2dd8326094a (commit)
via fbbae39aea715ccca384083611b6faa8250e068d (commit)
via 00e8a3ebd6c42824b0bfdd0ca96a6acd179a1194 (commit)
via e34ad6d48b840e52604ee9b3b0e50577b6d2c14a (commit)
via 8e7ce81608718bc14740240158ae60de162734de (commit)
via ce6188b38f1b684459bc645a13b951453c526108 (commit)
via 967e3ec6188b8a41e7f830bd13deda30afe5fa44 (commit)
via 25ae3625bc6da890c449bdca3d6b6f27a1fff0a2 (commit)
via 8bbc241df84bd1612644de8a8ff7d727a86ae485 (commit)
via 9dc528fb9ce46478f415722abfb9068d264154ce (commit)
via e5fc094ff9c928e8f7761de883bfa0ad34cf86c2 (commit)
via 2e6923cefcd6582ccbf9ef859ff92a91665b90ef (commit)
via cc5e14f7a89e3d5fc6211d156d631aa37b865597 (commit)
via ca2a79864dbc56af29eb95e01141fa86b2be8b44 (commit)
via 1127bca7125f7fb94fe3340a86e086307f80a9e2 (commit)
via 4ae9b5ea7462f0d2a7c87c8b3466ca100a310c74 (commit)
via 1bb01f6f6ca354f10def4d35850a782d2ae348a0 (commit)
via 8935b6716b3795bb889641a3127c2a701c6cac59 (commit)
via 625cc0a45da9ee3356df3263d03a16eca5051b03 (commit)
from 0c9ad616b7ce975f9cf0b26241c3a993f870c2b9 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit a8cf043db8f44604c7773e047a9dc2861e58462a
Merge: 0c9ad61 9a3a5db
Author: Marcin Siodelski <marcin at isc.org>
Date: Wed Sep 12 16:14:51 2012 +0200
Merge branch 'trac1959'
-----------------------------------------------------------------------
Summary of changes:
configure.ac | 1 +
src/lib/dhcp/iface_mgr.cc | 81 +-
src/lib/dhcp/iface_mgr.h | 23 +-
src/lib/dhcp/libdhcp++.cc | 26 +
src/lib/dhcp/libdhcp++.h | 24 +-
src/lib/dhcp/option.cc | 8 +
src/lib/dhcp/option.h | 36 +
src/lib/dhcp/tests/iface_mgr_unittest.cc | 104 ++
src/lib/dhcp/tests/libdhcp++_unittest.cc | 87 +
tests/tools/perfdhcp/Makefile.am | 32 +-
tests/tools/perfdhcp/command_options.cc | 198 ++-
tests/tools/perfdhcp/command_options.h | 247 +--
tests/tools/perfdhcp/localized_option.h | 109 +-
tests/tools/perfdhcp/main.cc | 50 +
tests/tools/perfdhcp/perf_pkt4.cc | 10 +-
tests/tools/perfdhcp/perf_pkt4.h | 26 +
tests/tools/perfdhcp/perf_pkt6.cc | 8 +
tests/tools/perfdhcp/perf_pkt6.h | 26 +
tests/tools/perfdhcp/pkt_transform.cc | 8 +-
tests/tools/perfdhcp/pkt_transform.h | 30 +
tests/tools/perfdhcp/stats_mgr.h | 154 +-
tests/tools/perfdhcp/templates/Makefile.am | 8 +
.../tools/perfdhcp/templates/discover-example.hex | 1 +
.../tools/perfdhcp/templates/request4-example.hex | 1 +
.../tools/perfdhcp/templates/request6-example.hex | 1 +
tests/tools/perfdhcp/templates/solicit-example.hex | 1 +
tests/tools/perfdhcp/test_control.cc | 1682 ++++++++++++++++++++
tests/tools/perfdhcp/test_control.h | 803 ++++++++++
tests/tools/perfdhcp/tests/Makefile.am | 3 +
.../tools/perfdhcp/tests/command_options_helper.h | 138 ++
.../perfdhcp/tests/command_options_unittest.cc | 380 +++--
tests/tools/perfdhcp/tests/perf_pkt4_unittest.cc | 46 +
tests/tools/perfdhcp/tests/stats_mgr_unittest.cc | 4 +-
.../tools/perfdhcp/tests/test_control_unittest.cc | 1011 ++++++++++++
34 files changed, 5019 insertions(+), 348 deletions(-)
create mode 100644 tests/tools/perfdhcp/main.cc
create mode 100644 tests/tools/perfdhcp/templates/Makefile.am
create mode 100644 tests/tools/perfdhcp/templates/discover-example.hex
create mode 100644 tests/tools/perfdhcp/templates/request4-example.hex
create mode 100644 tests/tools/perfdhcp/templates/request6-example.hex
create mode 100644 tests/tools/perfdhcp/templates/solicit-example.hex
create mode 100644 tests/tools/perfdhcp/test_control.cc
create mode 100644 tests/tools/perfdhcp/test_control.h
create mode 100644 tests/tools/perfdhcp/tests/command_options_helper.h
create mode 100644 tests/tools/perfdhcp/tests/test_control_unittest.cc
-----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index e46e182..2ead4b7 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 90f6353..7df809e 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 36ad0ea..d7d9f06 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<SocketInfo> SocketCollection;
+
/// @brief represents a single network interface
///
/// Iface structure represents network interface with all useful
@@ -89,6 +91,9 @@ public:
/// @param ifindex interface index (unique integer identifier)
Iface(const std::string& name, int ifindex);
+ /// @brief Closes all open sockets on interface.
+ void closeSockets();
+
/// @brief Returns full interface name as "ifname/ifindex" string.
///
/// @return string with interface name
@@ -192,11 +197,25 @@ public:
/// @return true if there was such socket, false otherwise
bool delSocket(uint16_t sockfd);
+ /// @brief Returns collection of all sockets added to interface.
+ ///
+ /// When new socket is created with @ref IfaceMgr::openSocket
+ /// it is added to sockets collection on particular interface.
+ /// If socket is opened by other means (e.g. function that does
+ /// not use @ref IfaceMgr::openSocket) it will not be available
+ /// in this collection. Note that functions like
+ /// @ref IfaceMgr::openSocketFromIface use
+ /// @ref IfaceMgr::openSocket internally.
+ /// The returned reference is only valid during the lifetime of
+ /// the IfaceMgr object that returned it.
+ ///
+ /// @return collection of sockets added to interface
+ const SocketCollection& getSockets() const { return sockets_; }
+
+ protected:
/// socket used to sending data
- /// TODO: this should be protected
SocketCollection sockets_;
- protected:
/// network interface name
std::string name_;
diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
index c054a4b..22cd47b 100644
--- a/src/lib/dhcp/libdhcp++.cc
+++ b/src/lib/dhcp/libdhcp++.cc
@@ -15,6 +15,7 @@
#include <boost/shared_array.hpp>
#include <boost/shared_ptr.hpp>
#include <util/buffer.h>
+#include <exceptions/exceptions.h>
#include <dhcp/libdhcp++.h>
#include "config.h"
#include <dhcp/dhcp4.h>
@@ -34,6 +35,31 @@ std::map<unsigned short, Option::Factory*> LibDHCP::v4factories_;
std::map<unsigned short, Option::Factory*> LibDHCP::v6factories_;
+OptionPtr
+LibDHCP::optionFactory(Option::Universe u,
+ uint16_t type,
+ const OptionBuffer& buf) {
+ FactoryMap::iterator it;
+ if (u == Option::V4) {
+ it = v4factories_.find(type);
+ if (it == v4factories_.end()) {
+ isc_throw(BadValue, "factory function not registered "
+ "for DHCP v4 option type " << type);
+ }
+ } else if (u == Option::V6) {
+ it = v6factories_.find(type);
+ if (it == v6factories_.end()) {
+ isc_throw(BadValue, "factory function not registered "
+ "for DHCPv6 option type " << type);
+ }
+ } else {
+ isc_throw(BadValue, "invalid universe specified (expected "
+ "Option::V4 or Option::V6");
+ }
+ return (it->second(u, type, buf));
+}
+
+
size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
isc::dhcp::Option::OptionCollection& options) {
size_t offset = 0;
diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h
index c7935c8..ae90701 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<unsigned short, Option::Factory*> FactoryMap;
+
+ /// @brief Factory function to create instance of option.
+ ///
+ /// Factory method creates instance of specified option. The option
+ /// to be created has to have corresponding factory function
+ /// registered with \ref LibDHCP::OptionFactoryRegister.
+ ///
+ /// @param u universe of the option (V4 or V6)
+ /// @param type option-type
+ /// @param buf option-buffer
+ /// @throw isc::InvalidOperation if there is no factory function
+ /// registered for specified option type.
+ /// @return instance of option.
+ static isc::dhcp::OptionPtr optionFactory(isc::dhcp::Option::Universe u,
+ uint16_t type,
+ const OptionBuffer& buf);
+
/// Builds collection of options.
///
/// Builds raw (on-wire) data for provided collection of options.
@@ -84,10 +104,10 @@ public:
Option::Factory * factory);
protected:
/// pointers to factories that produce DHCPv6 options
- static std::map<unsigned short, Option::Factory*> v4factories_;
+ static FactoryMap v4factories_;
/// pointers to factories that produce DHCPv6 options
- static std::map<unsigned short, Option::Factory*> v6factories_;
+ static FactoryMap v6factories_;
};
}
diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc
index 0c71606..fb441f9 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 0662967..080a869 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 5562551..ad8dba9 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<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // container for initialized socket descriptors
+ std::list<uint16_t> init_sockets;
+
+ // create socket #1
+ int socket1 = 0;
+ ASSERT_NO_THROW(
+ socket1 = ifacemgr->openSocketFromIface(LOOPBACK, PORT1, AF_INET);
+ );
+ ASSERT_GT(socket1, 0);
+ init_sockets.push_back(socket1);
+
+ // create socket #2
+ IOAddress loAddr("127.0.0.1");
+ int socket2 = 0;
+ ASSERT_NO_THROW(
+ socket2 = ifacemgr->openSocketFromRemoteAddress(loAddr, PORT2);
+ );
+ ASSERT_GT(socket2, 0);
+ init_sockets.push_back(socket2);
+
+ // Get loopback interface. If we don't find one we are unable to run
+ // this test but we don't want to fail.
+ IfaceMgr::Iface* iface_ptr = ifacemgr->getIface(LOOPBACK);
+ if (iface_ptr == NULL) {
+ cout << "Local loopback interface not found. Skipping test. " << endl;
+ return;
+ }
+ // Once sockets have been sucessfully opened, they are supposed to
+ // be on the list. Here we start to test if all expected sockets
+ // are on the list and no other (unexpected) socket is there.
+ IfaceMgr::SocketCollection sockets = iface_ptr->getSockets();
+ int matched_sockets = 0;
+ for (std::list<uint16_t>::iterator init_sockets_it =
+ init_sockets.begin();
+ init_sockets_it != init_sockets.end(); ++init_sockets_it) {
+ // Set socket descriptors non blocking in order to be able
+ // to call recv() on them without hang.
+ int flags = fcntl(*init_sockets_it, F_GETFL, 0);
+ ASSERT_GE(flags, 0);
+ ASSERT_GE(fcntl(*init_sockets_it, F_SETFL, flags | O_NONBLOCK), 0);
+ // recv() is expected to result in EWOULDBLOCK error on non-blocking
+ // socket in case socket is valid but simply no data are coming in.
+ char buf;
+ recv(*init_sockets_it, &buf, 1, MSG_PEEK);
+ EXPECT_EQ(EWOULDBLOCK, errno);
+ // Apart from the ability to use the socket we want to make
+ // sure that socket on the list is the one that we created.
+ for (IfaceMgr::SocketCollection::const_iterator socket_it =
+ sockets.begin(); socket_it != sockets.end(); ++socket_it) {
+ if (*init_sockets_it == socket_it->sockfd_) {
+ // This socket is the one that we created.
+ ++matched_sockets;
+ break;
+ }
+ }
+ }
+ // all created sockets have been matched if this condition works.
+ EXPECT_EQ(sockets.size(), matched_sockets);
+
+ // closeSockets() is the other function that we want to test. It
+ // is supposed to close all sockets so as we will not be able to use
+ // them anymore communication.
+ ifacemgr->closeSockets();
+
+ // closed sockets are supposed to be removed from the list
+ sockets = iface_ptr->getSockets();
+ ASSERT_EQ(0, sockets.size());
+
+ // We are still in posession of socket descriptors that we created
+ // on the beginning of this test. We can use them to check whether
+ // closeSockets() only removed them from the list or they have been
+ // really closed.
+ for (std::list<uint16_t>::const_iterator init_sockets_it =
+ init_sockets.begin();
+ init_sockets_it != init_sockets.end(); ++init_sockets_it) {
+ // recv() must result in error when using invalid socket.
+ char buf;
+ recv(*init_sockets_it, &buf, 1, MSG_PEEK);
+ // EWOULDBLOCK would mean that socket is valid/open but
+ // simply no data is received so we have to check for
+ // other errors.
+ EXPECT_NE(EWOULDBLOCK, errno);
+ }
+}
+
TEST_F(IfaceMgrTest, sockets6) {
// testing socket operation in a portable way is tricky
// without interface detection implemented
@@ -317,6 +406,21 @@ TEST_F(IfaceMgrTest, socketsFromRemoteAddress) {
);
EXPECT_GT(socket2, 0);
close(socket2);
+
+ // The following test is currently disabled for OSes other than
+ // Linux because interface detection is not implemented on them.
+ // @todo enable this test for all OSes once interface detection
+ // is implemented.
+#if defined(OS_LINUX)
+ // Open v4 socket to connect to broadcast address.
+ int socket3 = 0;
+ IOAddress bcastAddr("255.255.255.255");
+ EXPECT_NO_THROW(
+ socket3 = ifacemgr->openSocketFromRemoteAddress(bcastAddr, PORT2);
+ );
+ EXPECT_GT(socket3, 0);
+ close(socket3);
+#endif
}
// TODO: disabled due to other naming on various systems
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index 7e18be6..d17eb65 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -18,6 +18,8 @@
#include <arpa/inet.h>
#include <gtest/gtest.h>
#include <util/buffer.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
#include <dhcp/libdhcp++.h>
#include "config.h"
@@ -31,6 +33,19 @@ class LibDhcpTest : public ::testing::Test {
public:
LibDhcpTest() {
}
+
+ /// @brief Generic factory function to create any option.
+ ///
+ /// Generic factory function to create any option.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param type option-type
+ /// @param buf option-buffer
+ static OptionPtr genericOptionFactory(Option::Universe u, uint16_t type,
+ const OptionBuffer& buf) {
+ Option* option = new Option(u, type, buf);
+ return OptionPtr(option);
+ }
};
static const uint8_t packed[] = {
@@ -41,6 +56,78 @@ static const uint8_t packed[] = {
1, 1, 0, 1, 114 // opt5 (5 bytes)
};
+TEST(LibDhcpTest, optionFactory) {
+ OptionBuffer buf;
+ // Factory functions for specific options must be registered before
+ // they can be used to create options instances. Otherwise exception
+ // is rised.
+ EXPECT_THROW(LibDHCP::optionFactory(Option::V4, DHO_SUBNET_MASK, buf),
+ isc::BadValue);
+
+ // Let's register some factory functions (two v4 and one v6 function).
+ // Registration may trigger exception if function for the specified
+ // option has been registered already.
+ ASSERT_NO_THROW(
+ LibDHCP::OptionFactoryRegister(Option::V4, DHO_SUBNET_MASK,
+ &LibDhcpTest::genericOptionFactory);
+ );
+ ASSERT_NO_THROW(
+ LibDHCP::OptionFactoryRegister(Option::V4, DHO_TIME_OFFSET,
+ &LibDhcpTest::genericOptionFactory);
+ );
+ ASSERT_NO_THROW(
+ LibDHCP::OptionFactoryRegister(Option::V6, D6O_CLIENTID,
+ &LibDhcpTest::genericOptionFactory);
+ );
+
+ // Invoke factory functions for all options (check if registration
+ // was successful).
+ OptionPtr opt_subnet_mask;
+ opt_subnet_mask = LibDHCP::optionFactory(Option::V4,
+ DHO_SUBNET_MASK,
+ buf);
+ // Check if non-NULL DHO_SUBNET_MASK option pointer has been returned.
+ ASSERT_TRUE(opt_subnet_mask);
+ // Validate if type and universe is correct.
+ EXPECT_EQ(Option::V4, opt_subnet_mask->getUniverse());
+ EXPECT_EQ(DHO_SUBNET_MASK, opt_subnet_mask->getType());
+ // Expect that option does not have content..
+ EXPECT_EQ(0, opt_subnet_mask->len() - opt_subnet_mask->getHeaderLen());
+
+ // Fill the time offset buffer with 4 bytes of data. Each byte set to 1.
+ OptionBuffer time_offset_buf(4, 1);
+ OptionPtr opt_time_offset;
+ opt_time_offset = LibDHCP::optionFactory(Option::V4,
+ DHO_TIME_OFFSET,
+ time_offset_buf);
+ // Check if non-NULL DHO_TIME_OFFSET option pointer has been returned.
+ ASSERT_TRUE(opt_time_offset);
+ // Validate if option length, type and universe is correct.
+ EXPECT_EQ(Option::V4, opt_time_offset->getUniverse());
+ EXPECT_EQ(DHO_TIME_OFFSET, opt_time_offset->getType());
+ EXPECT_EQ(time_offset_buf.size(),
+ opt_time_offset->len() - opt_time_offset->getHeaderLen());
+ // Validate data in the option.
+ EXPECT_TRUE(std::equal(time_offset_buf.begin(), time_offset_buf.end(),
+ opt_time_offset->getData().begin()));
+
+ // Fill the client id buffer with 20 bytes of data. Each byte set to 2.
+ OptionBuffer clientid_buf(20, 2);
+ OptionPtr opt_clientid;
+ opt_clientid = LibDHCP::optionFactory(Option::V6,
+ D6O_CLIENTID,
+ clientid_buf);
+ // Check if non-NULL D6O_CLIENTID option pointer has been returned.
+ ASSERT_TRUE(opt_clientid);
+ // Validate if option length, type and universe is correct.
+ EXPECT_EQ(Option::V6, opt_clientid->getUniverse());
+ EXPECT_EQ(D6O_CLIENTID, opt_clientid->getType());
+ EXPECT_EQ(clientid_buf.size(), opt_clientid->len() - opt_clientid->getHeaderLen());
+ // Validate data in the option.
+ EXPECT_TRUE(std::equal(clientid_buf.begin(), clientid_buf.end(),
+ opt_clientid->getData().begin()));
+}
+
TEST(LibDhcpTest, packOptions6) {
OptionBuffer buf(512);
isc::dhcp::Option::OptionCollection opts; // list of options
diff --git a/tests/tools/perfdhcp/Makefile.am b/tests/tools/perfdhcp/Makefile.am
index 7c8064e..0532f27 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 09393bb..24e7e79 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 <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
@@ -20,9 +21,11 @@
#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
-#include "exceptions/exceptions.h"
-
+#include <exceptions/exceptions.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/iface_mgr.h>
#include "command_options.h"
using namespace std;
@@ -54,9 +57,10 @@ CommandOptions::reset() {
rate_ = 0;
report_delay_ = 0;
clients_num_ = 0;
- mac_prefix_.assign(mac, mac + 6);
- base_.resize(0);
- num_request_.resize(0);
+ mac_template_.assign(mac, mac + 6);
+ duid_template_.clear();
+ base_.clear();
+ num_request_.clear();
period_ = 0;
drop_time_set_ = 0;
drop_time_.assign(dt, dt + 2);
@@ -81,6 +85,8 @@ CommandOptions::reset() {
diags_.clear();
wrapped_.clear();
server_name_.clear();
+ generateDuidTemplate();
+ commandline_.clear();
}
void
@@ -127,9 +133,16 @@ CommandOptions::initialize(int argc, char** argv) {
int offset_arg = 0; // Temporary variable holding offset arguments
std::string sarg; // Temporary variable for string args
+ std::ostringstream stream;
+ stream << "perfdhcp";
+
// In this section we collect argument values from command line
// they will be tuned and validated elsewhere
while((opt = getopt(argc, argv, "hv46r:t:R:b:n:p:d:D:l:P:a:L:s:iBc1T:X:O:E:S:I:x:w:")) != -1) {
+ stream << " -" << opt;
+ if (optarg) {
+ stream << " " << optarg;
+ }
switch (opt) {
case 'v':
version();
@@ -219,6 +232,7 @@ CommandOptions::initialize(int argc, char** argv) {
case 'l':
localname_ = std::string(optarg);
+ initIsInterface();
break;
case 'L':
@@ -312,6 +326,8 @@ CommandOptions::initialize(int argc, char** argv) {
}
}
+ std::cout << "Running: " << stream.str() << std::endl;
+
// If the IP version was not specified in the
// command line, assume IPv4.
if (ipversion_ == 0) {
@@ -351,7 +367,27 @@ CommandOptions::initialize(int argc, char** argv) {
}
}
- // TODO handle -l option with IfaceManager when it is created
+ // Handle the local '-l' address/interface
+ if (!localname_.empty()) {
+ if (server_name_.empty()) {
+ if (is_interface_ && (ipversion_ == 4)) {
+ broadcast_ = 1;
+ server_name_ = "255.255.255.255";
+ } else if (is_interface_ && (ipversion_ == 6)) {
+ server_name_ = "FF02::1:2";
+ }
+ }
+ }
+ if (server_name_.empty()) {
+ isc_throw(InvalidParameter,
+ "without an inteface server is required");
+ }
+
+ // If DUID is not specified from command line we need to
+ // generate one.
+ if (duid_template_.size() == 0) {
+ generateDuidTemplate();
+ }
}
void
@@ -377,6 +413,17 @@ CommandOptions::initClientsNum() {
}
void
+CommandOptions::initIsInterface() {
+ is_interface_ = false;
+ if (!localname_.empty()) {
+ dhcp::IfaceMgr& iface_mgr = dhcp::IfaceMgr::instance();
+ if (iface_mgr.getIface(localname_) != NULL) {
+ is_interface_ = true;
+ }
+ }
+}
+
+void
CommandOptions::decodeBase(const std::string& base) {
std::string b(base);
boost::algorithm::to_lower(b);
@@ -402,7 +449,7 @@ CommandOptions::decodeMac(const std::string& base) {
// Decode mac address to vector of uint8_t
std::istringstream s1(base.substr(found + 1));
std::string token;
- mac_prefix_.clear();
+ mac_template_.clear();
// Get pieces of MAC address separated with : (or even ::)
while (std::getline(s1, token, ':')) {
unsigned int ui = 0;
@@ -417,16 +464,17 @@ CommandOptions::decodeMac(const std::string& base) {
}
// If conversion succeeded store byte value
- mac_prefix_.push_back(ui);
+ mac_template_.push_back(ui);
}
}
// MAC address must consist of 6 octets, otherwise it is invalid
- check(mac_prefix_.size() != 6, errmsg);
+ check(mac_template_.size() != 6, errmsg);
}
void
CommandOptions::decodeDuid(const std::string& base) {
// Strip argument from duid=
+ std::vector<uint8_t> duid_template;
size_t found = base.find('=');
check(found == std::string::npos, "expected -b<base> format for duid is -b duid=<duid>");
std::string b = base.substr(found + 1);
@@ -446,8 +494,44 @@ CommandOptions::decodeDuid(const std::string& base) {
isc_throw(isc::InvalidParameter,
"invalid characters in DUID provided, exepected hex digits");
}
- duid_prefix_.push_back(static_cast<uint8_t>(ui));
+ duid_template.push_back(static_cast<uint8_t>(ui));
}
+ // @todo Get rid of this limitation when we manage add support
+ // for DUIDs other than LLT. Shorter DUIDs may be useful for
+ // server testing purposes.
+ check(duid_template.size() < 6, "DUID must be at least 6 octets long");
+ // Assign the new duid only if successfully generated.
+ std::swap(duid_template, duid_template_);
+}
+
+void
+CommandOptions::generateDuidTemplate() {
+ using namespace boost::posix_time;
+ // Duid template will be most likely generated only once but
+ // it is ok if it is called more then once so we simply
+ // regenerate it and discard previous value.
+ duid_template_.clear();
+ const uint8_t duid_template_len = 14;
+ duid_template_.resize(duid_template_len);
+ // The first four octets consist of DUID LLT and hardware type.
+ duid_template_[0] = DUID_LLT >> 8;
+ duid_template_[1] = DUID_LLT & 0xff;
+ duid_template_[2] = HWTYPE_ETHERNET >> 8;
+ duid_template_[3] = HWTYPE_ETHERNET & 0xff;
+
+ // As described in RFC3315: 'the time value is the time
+ // that the DUID is generated represented in seconds
+ // since midnight (UTC), January 1, 2000, modulo 2^32.'
+ ptime now = microsec_clock::universal_time();
+ ptime duid_epoch(from_iso_string("20000101T000000"));
+ time_period period(duid_epoch, now);
+ uint32_t duration_sec = htonl(period.length().total_seconds());
+ memcpy(&duid_template_[4], &duration_sec, 4);
+
+ // Set link layer address (6 octets). This value may be
+ // randomized before sending a packet to simulate different
+ // clients.
+ memcpy(&duid_template_[8], &mac_template_[0], 6);
}
uint8_t
@@ -565,6 +649,98 @@ CommandOptions::nonEmptyString(const std::string& errmsg) const {
}
void
+CommandOptions::printCommandLine() const {
+ std::cout << "IPv" << static_cast<int>(ipversion_) << std::endl;
+ if (exchange_mode_ == DO_SA) {
+ if (ipversion_ == 4) {
+ std::cout << "DISCOVER-OFFER only" << std::endl;
+ } else {
+ std::cout << "SOLICIT-ADVERETISE only" << std::endl;
+ }
+ }
+ if (rate_ != 0) {
+ std::cout << "rate[1/s]=" << rate_ << std::endl;
+ }
+ if (report_delay_ != 0) {
+ std::cout << "report[s]=" << report_delay_ << std::endl;
+ }
+ if (clients_num_ != 0) {
+ std::cout << "clients=" << clients_num_ << std::endl;
+ }
+ for (int i = 0; i < base_.size(); ++i) {
+ std::cout << "base[" << i << "]=" << base_[i] << std::endl;
+ }
+ for (int i = 0; i < num_request_.size(); ++i) {
+ std::cout << "num-request[" << i << "]=" << num_request_[i] << std::endl;
+ }
+ if (period_ != 0) {
+ std::cout << "test-period=" << period_ << std::endl;
+ }
+ for (int i = 0; i < drop_time_.size(); ++i) {
+ std::cout << "drop-time[" << i << "]=" << drop_time_[i] << std::endl;
+ }
+ for (int i = 0; i < max_drop_.size(); ++i) {
+ std::cout << "max-drop{" << i << "]=" << max_drop_[i] << std::endl;
+ }
+ for (int i = 0; i < max_pdrop_.size(); ++i) {
+ std::cout << "max-pdrop{" << i << "]=" << max_pdrop_[i] << std::endl;
+ }
+ if (preload_ != 0) {
+ std::cout << "preload=" << preload_ << std::endl;
+ }
+ std::cout << "aggressivity=" << aggressivity_ << std::endl;
+ if (getLocalPort() != 0) {
+ std::cout << "local-port=" << local_port_ << std::endl;
+ }
+ if (seeded_) {
+ std::cout << "seed=" << seed_ << std::endl;
+ }
+ if (broadcast_) {
+ std::cout << "broadcast" << std::endl;
+ }
+ if (rapid_commit_) {
+ std::cout << "rapid-commit" << std::endl;
+ }
+ if (use_first_) {
+ std::cout << "use-first" << std::endl;
+ }
+ for (int i = 0; i < template_file_.size(); ++i) {
+ std::cout << "template-file[" << i << "]=" << template_file_[i] << std::endl;
+ }
+ for (int i = 0; i < xid_offset_.size(); ++i) {
+ std::cout << "xid-offset[" << i << "]=" << xid_offset_[i] << std::endl;
+ }
+ if (elp_offset_ != 0) {
+ std::cout << "elp-offset=" << elp_offset_ << std::endl;
+ }
+ for (int i = 0; i < rnd_offset_.size(); ++i) {
+ std::cout << "rnd-offset[" << i << "]=" << rnd_offset_[i] << std::endl;
+ }
+ if (sid_offset_ != 0) {
+ std::cout << "sid-offset=" << sid_offset_ << std::endl;
+ }
+ if (rip_offset_ != 0) {
+ std::cout << "rip-offset=" << rip_offset_ << std::endl;
+ }
+ if (!diags_.empty()) {
+ std::cout << "diagnostic-selectors=" << diags_ << std::endl;
+ }
+ if (!wrapped_.empty()) {
+ std::cout << "wrapped=" << wrapped_ << std::endl;
+ }
+ if (!localname_.empty()) {
+ if (is_interface_) {
+ std::cout << "interface=" << localname_ << std::endl;
+ } else {
+ std::cout << "local-addr=" << localname_ << std::endl;
+ }
+ }
+ if (!server_name_.empty()) {
+ std::cout << "server=" << server_name_ << std::endl;
+ }
+}
+
+void
CommandOptions::usage() const {
fprintf(stdout, "%s",
"perfdhcp [-hv] [-4|-6] [-r<rate>] [-t<report>] [-R<range>] [-b<base>]\n"
@@ -691,7 +867,7 @@ CommandOptions::usage() const {
void
CommandOptions::version() const {
- fprintf(stdout, "version 0.01\n");
+ std::cout << "VERSION: " << VERSION << std::endl;
}
diff --git a/tests/tools/perfdhcp/command_options.h b/tests/tools/perfdhcp/command_options.h
index 9196857..b9e2b9e 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<uint8_t> getMacPrefix() const { return mac_prefix_; }
+ /// \return MAC address template to simulate different clients.
+ std::vector<uint8_t> getMacTemplate() const { return mac_template_; }
- /// \brief Returns DUID prefix
+ /// \brief Returns DUID template.
///
- /// \return DUID prefix to simulate different clients
- std::vector<uint8_t> getDuidPrefix() const { return duid_prefix_; }
+ /// \return DUID template to simulate different clients.
+ std::vector<uint8_t> getDuidTemplate() const { return duid_template_; }
- /// \brief Returns base values
+ /// \brief Returns base values.
///
- /// \return all base values specified
+ /// \return all base values specified.
std::vector<std::string> getBase() const { return base_; }
- /// \brief Returns maximum number of exchanges
+ /// \brief Returns maximum number of exchanges.
///
- /// \return number of exchange requests before test is aborted
+ /// \return number of exchange requests before test is aborted.
std::vector<int> getNumRequests() const { return num_request_; }
- /// \brief Returns test period
+ /// \brief Returns test period.
///
- /// \return test period before it is aborted
+ /// \return test period before it is aborted.
int getPeriod() const { return period_; }
/// \brief Returns drop time
@@ -114,136 +114,139 @@ public:
/// The method returns maximum time elapsed from
/// sending the packet before it is assumed dropped.
///
- /// \return return time before request is assumed dropped
+ /// \return return time before request is assumed dropped.
std::vector<double> getDropTime() const { return drop_time_; }
- /// \brief Returns maximum drops number
+ /// \brief Returns maximum drops number.
///
/// Returns maximum number of packet drops before
/// aborting a test.
///
- /// \return maximum number of dropped requests
+ /// \return maximum number of dropped requests.
std::vector<int> getMaxDrop() const { return max_drop_; }
- /// \brief Returns maximal percentage of drops
+ /// \brief Returns maximal percentage of drops.
///
/// Returns maximal percentage of packet drops
/// before aborting a test.
///
- /// \return maximum percentage of lost requests
+ /// \return maximum percentage of lost requests.
std::vector<double> getMaxDropPercentage() const { return max_pdrop_; }
- /// \brief Returns local address or interface name
+ /// \brief Returns local address or interface name.
///
- /// \return local address or interface name
+ /// \return local address or interface name.
std::string getLocalName() const { return localname_; }
- /// \brief Checks if interface name was used
+ /// \brief Checks if interface name was used.
///
/// The method checks if interface name was used
/// rather than address.
///
- /// \return true if interface name was used
+ /// \return true if interface name was used.
bool isInterface() const { return is_interface_; }
- /// \brief Returns number of preload exchanges
+ /// \brief Returns number of preload exchanges.
///
- /// \return number of preload exchanges
+ /// \return number of preload exchanges.
int getPreload() const { return preload_; }
- /// \brief Returns aggressivity value
+ /// \brief Returns aggressivity value.
///
- /// \return aggressivity value
+ /// \return aggressivity value.
int getAggressivity() const { return aggressivity_; }
- /// \brief Returns local port number
+ /// \brief Returns local port number.
///
- /// \return local port number
+ /// \return local port number.
int getLocalPort() const { return local_port_; }
- /// \brief Checks if seed provided
+ /// \brief Checks if seed provided.
///
- /// \return true if seed was provided
+ /// \return true if seed was provided.
bool isSeeded() const { return seeded_; }
- /// \brief Returns radom seed
+ /// \brief Returns radom seed.
///
- /// \return random seed
+ /// \return random seed.
uint32_t getSeed() const { return seed_; }
- /// \brief Checks if broadcast address is to be used
+ /// \brief Checks if broadcast address is to be used.
///
- /// \return true if broadcast address is to be used
+ /// \return true if broadcast address is to be used.
bool isBroadcast() const { return broadcast_; }
- /// \brief Check if rapid commit option used
+ /// \brief Check if rapid commit option used.
///
- /// \return true if rapid commit option is used
+ /// \return true if rapid commit option is used.
bool isRapidCommit() const { return rapid_commit_; }
- /// \brief Check if server-ID to be taken from first package
+ /// \brief Check if server-ID to be taken from first package.
///
- /// \return true if server-iD to be taken from first package
+ /// \return true if server-iD to be taken from first package.
bool isUseFirst() const { return use_first_; }
- /// \brief Returns template file names
+ /// \brief Returns template file names.
///
- /// \return template file names
+ /// \return template file names.
std::vector<std::string> getTemplateFiles() const { return template_file_; }
- /// brief Returns template offsets for xid
+ /// brief Returns template offsets for xid.
///
- /// \return template offsets for xid
+ /// \return template offsets for xid.
std::vector<int> getTransactionIdOffset() const { return xid_offset_; }
- /// \brief Returns template offsets for rnd
+ /// \brief Returns template offsets for rnd.
///
- /// \return template offsets for rnd
+ /// \return template offsets for rnd.
std::vector<int> getRandomOffset() const { return rnd_offset_; }
- /// \brief Returns template offset for elapsed time
+ /// \brief Returns template offset for elapsed time.
///
- /// \return template offset for elapsed time
+ /// \return template offset for elapsed time.
int getElapsedTimeOffset() const { return elp_offset_; }
- /// \brief Returns template offset for server-ID
+ /// \brief Returns template offset for server-ID.
///
- /// \return template offset for server-ID
+ /// \return template offset for server-ID.
int getServerIdOffset() const { return sid_offset_; }
- /// \brief Returns template offset for requested IP
+ /// \brief Returns template offset for requested IP.
///
- /// \return template offset for requested IP
+ /// \return template offset for requested IP.
int getRequestedIpOffset() const { return rip_offset_; }
- /// \brief Returns diagnostic selectors
+ /// \brief Returns diagnostic selectors.
///
- /// \return diagnostics selector
+ /// \return diagnostics selector.
std::string getDiags() const { return diags_; }
- /// \brief Returns wrapped command
+ /// \brief Returns wrapped command.
///
- /// \return wrapped command (start/stop)
+ /// \return wrapped command (start/stop).
std::string getWrapped() const { return wrapped_; }
- /// \brief Returns server name
+ /// \brief Returns server name.
///
- /// \return server name
+ /// \return server name.
std::string getServerName() const { return server_name_; }
+
+ /// \brief Print command line arguments.
+ void printCommandLine() const;
- /// \brief Print usage
+ /// \brief Print usage.
///
- /// Prints perfdhcp usage
+ /// Prints perfdhcp usage.
void usage() const;
- /// \brief Print program version
+ /// \brief Print program version.
///
- /// Prints perfdhcp version
+ /// Prints perfdhcp version.
void version() const;
private:
- /// \brief Default Constructor
+ /// \brief Default Constructor.
///
/// Private constructor as this is a singleton class.
/// Use CommandOptions::instance() to get instance of it.
@@ -251,57 +254,64 @@ private:
reset();
}
- /// \brief Initializes class members based command line
+ /// \brief Initializes class members based on the command line.
///
- /// Reads each command line parameter and sets class member values
+ /// Reads each command line parameter and sets class member values.
///
/// \param argc Argument count passed to main().
/// \param argv Argument value array passed to main().
- /// \throws isc::InvalidParameter if command line options initialization fails
+ /// \throws isc::InvalidParameter if command line options initialization fails.
void initialize(int argc, char** argv);
- /// \brief Validates initialized options
+ /// \brief Validates initialized options.
///
- /// \throws isc::InvalidParameter if command line validation fails
+ /// \throws isc::InvalidParameter if command line validation fails.
void validate() const;
- /// \brief Throws !InvalidParameter exception if condition is true
+ /// \brief Throws !InvalidParameter exception if condition is true.
///
/// Convenience function that throws an InvalidParameter exception if
- /// the condition argument is true
+ /// the condition argument is true.
///
- /// \param condition Condition to be checked
- /// \param errmsg Error message in exception
- /// \throws isc::InvalidParameter if condition argument true
+ /// \param condition Condition to be checked.
+ /// \param errmsg Error message in exception.
+ /// \throws isc::InvalidParameter if condition argument true.
inline void check(bool condition, const std::string& errmsg) const;
- /// \brief Casts command line argument to positive integer
+ /// \brief Casts command line argument to positive integer.
///
- /// \param errmsg Error message if lexical cast fails
- /// \throw InvalidParameter if lexical cast fails
+ /// \param errmsg Error message if lexical cast fails.
+ /// \throw InvalidParameter if lexical cast fails.
int positiveInteger(const std::string& errmsg) const;
- /// \brief Casts command line argument to non-negative integer
+ /// \brief Casts command line argument to non-negative integer.
///
- /// \param errmsg Error message if lexical cast fails
- /// \throw InvalidParameter if lexical cast fails
+ /// \param errmsg Error message if lexical cast fails.
+ /// \throw InvalidParameter if lexical cast fails.
int nonNegativeInteger(const std::string& errmsg) const;
- /// \brief Returns command line string if it is not empty
+ /// \brief Returns command line string if it is not empty.
///
- /// \param errmsg Error message if string is empty
- /// \throw InvalidParameter if string is empty
+ /// \param errmsg Error message if string is empty.
+ /// \throw InvalidParameter if string is empty.
std::string nonEmptyString(const std::string& errmsg) const;
- /// \brief Set number of clients
+ /// \brief Set number of clients.
///
/// Interprets the getopt() "opt" global variable as the number of clients
/// (a non-negative number). This value is specified by the "-R" switch.
///
- /// \throw InvalidParameter if -R<value> is wrong
+ /// \throw InvalidParameter if -R<value> is wrong.
void initClientsNum();
- /// \brief Decodes base provided with -b<base>
+ /// \brief Sets value indicating if interface name was given.
+ ///
+ /// Method checks if the command line argument given with
+ /// '-l' option is the interface name. The is_interface_ member
+ /// is set accordingly.
+ void initIsInterface();
+
+ /// \brief Decodes base provided with -b<base>.
///
/// Function decodes argument of -b switch, which
/// specifies a base value used to generate unique
@@ -311,39 +321,47 @@ private:
/// - -b mac=00:01:02:03:04:05
/// - -b duid=0F1234 (duid can be up to 128 hex digits)
// Function will decode 00:01:02:03:04:05 and/or
- /// 0F1234 respectively and initialize mac_prefix_
- /// and/or duid_prefix_ members
+ /// 0F1234 respectively and initialize mac_template_
+ /// and/or duid_template_ members.
///
- /// \param base Base in string format
- /// \throws isc::InvalidParameter if base is invalid
+ /// \param base Base in string format.
+ /// \throws isc::InvalidParameter if base is invalid.
void decodeBase(const std::string& base);
- /// \brief Decodes base MAC address provided with -b<base>
+ /// \brief Decodes base MAC address provided with -b<base>.
///
/// Function decodes parameter given as -b mac=00:01:02:03:04:05
- /// The function will decode 00:01:02:03:04:05 initialize mac_prefix_
+ /// The function will decode 00:01:02:03:04:05 initialize mac_template_
/// class member.
- /// Provided MAC address is for example only
+ /// Provided MAC address is for example only.
///
- /// \param base Base string given as -b mac=00:01:02:03:04:05
- /// \throws isc::InvalidParameter if mac address is invalid
+ /// \param base Base string given as -b mac=00:01:02:03:04:05.
+ /// \throws isc::InvalidParameter if mac address is invalid.
void decodeMac(const std::string& base);
- /// \brief Decodes base DUID provided with -b<base>
+ /// \brief Decodes base DUID provided with -b<base>.
///
- /// Function decodes parameter given as -b duid=0F1234
- /// The function will decode 0F1234 and initialize duid_prefix_
+ /// Function decodes parameter given as -b duid=0F1234.
+ /// The function will decode 0F1234 and initialize duid_template_
/// class member.
/// Provided DUID is for example only.
///
- /// \param base Base string given as -b duid=0F1234
- /// \throws isc::InvalidParameter if DUID is invalid
+ /// \param base Base string given as -b duid=0F1234.
+ /// \throws isc::InvalidParameter if DUID is invalid.
void decodeDuid(const std::string& base);
+
+ /// \brief Generates DUID-LLT (based on link layer address).
+ ///
+ /// Function generates DUID based on link layer address and
+ /// initiates duid_template_ value with it.
+ /// \todo add support to generate DUIDs other than based on
+ /// 6-octets long MACs (e.g. DUID-UUID.
+ void generateDuidTemplate();
- /// \brief Converts two-digit hexadecimal string to a byte
+ /// \brief Converts two-digit hexadecimal string to a byte.
///
- /// \param hex_text Hexadecimal string e.g. AF
- /// \throw isc::InvalidParameter if string does not represent hex byte
+ /// \param hex_text Hexadecimal string e.g. AF.
+ /// \throw isc::InvalidParameter if string does not represent hex byte.
uint8_t convertHexString(const std::string& hex_text) const;
uint8_t ipversion_; ///< IP protocol version to be used, expected values are:
@@ -353,9 +371,9 @@ private:
int report_delay_; ///< Delay between generation of two consecutive
///< performance reports
uint32_t clients_num_; ///< Number of simulated clients (aka randomization range).
- std::vector<uint8_t> mac_prefix_; ///< MAC address prefix used to generate unique DUIDs
+ std::vector<uint8_t> mac_template_; ///< MAC address template used to generate unique DUIDs
///< for simulated clients.
- std::vector<uint8_t> duid_prefix_; ///< DUID prefix used to generate unique DUIDs for
+ std::vector<uint8_t> duid_template_; ///< DUID template used to generate unique DUIDs for
///< simulated clients
std::vector<std::string> base_; ///< Collection of base values specified with -b<value>
///< options. Supported "bases" are mac=<mac> and duid=<duid>
@@ -404,6 +422,7 @@ private:
std::string wrapped_; ///< Wrapped command specified as -w<value>. Expected
///< values are start and stop.
std::string server_name_; ///< Server name specified as last argument of command line.
+ std::string commandline_; ///< Entire command line as typed in by the user.
};
} // namespace perfdhcp
diff --git a/tests/tools/perfdhcp/localized_option.h b/tests/tools/perfdhcp/localized_option.h
index 5374684..336e083 100644
--- a/tests/tools/perfdhcp/localized_option.h
+++ b/tests/tools/perfdhcp/localized_option.h
@@ -16,6 +16,8 @@
#define __LOCALIZED_OPTION_H
#include <dhcp/pkt6.h>
+#include <dhcp/option6_ia.h>
+#include <util/buffer.h>
namespace isc {
namespace perfdhcp {
@@ -42,69 +44,86 @@ namespace perfdhcp {
///
class LocalizedOption : public dhcp::Option {
public:
- /// \brief Constructor, sets default (0) option offset
- ///
- /// \param u specifies universe (V4 or V6)
- /// \param type option type (0-255 for V4 and 0-65535 for V6)
- /// \param data content of the option
- LocalizedOption(dhcp::Option::Universe u,
- uint16_t type,
- const dhcp::OptionBuffer& data) :
- dhcp::Option(u, type, data),
- offset_(0) {
- }
-
- /// \brief Constructor, used to create localized option from buffer
+ /// \brief Constructor, used to create localized option from buffer.
///
- /// \param u specifies universe (V4 or V6)
- /// \param type option type (0-255 for V4 and 0-65535 for V6)
- /// \param data content of the option
- /// \param offset location of option in a packet (zero is default)
+ /// This constructor creates localized option using whole provided
+ /// option buffer.
+ ///
+ /// \param u universe (V4 or V6).
+ /// \param type option type (0-255 for V4 and 0-65535 for V6).
+ /// Option values 0 and 255 (v4) and 0 (v6) are not valid option
+ /// codes but they are accepted here for the server testing purposes.
+ /// \param data content of the option.
+ /// \param offset location of option in a packet (zero is default).
LocalizedOption(dhcp::Option::Universe u,
uint16_t type,
const dhcp::OptionBuffer& data,
- const size_t offset) :
+ const size_t offset = 0) :
dhcp::Option(u, type, data),
- offset_(offset) {
+ offset_(offset), option_valid_(true) {
}
- /// \brief Constructor, sets default (0) option offset
+ /// \brief Constructor, used to create option from buffer iterators.
///
- /// This contructor is similar to the previous one, but it does not take
- /// the whole vector<uint8_t>, but rather subset of it.
+ /// This constructor creates localized option using part of the
+ /// option buffer pointed by iterators.
///
/// \param u specifies universe (V4 or V6)
/// \param type option type (0-255 for V4 and 0-65535 for V6)
/// \param first iterator to the first element that should be copied
/// \param last iterator to the next element after the last one
/// to be copied.
+ /// \param offset offset of option in a packet (zero is default)
LocalizedOption(dhcp::Option::Universe u,
uint16_t type,
dhcp::OptionBufferConstIter first,
- dhcp::OptionBufferConstIter last) :
+ dhcp::OptionBufferConstIter last,
+ const size_t offset = 0) :
dhcp::Option(u, type, first, last),
- offset_(0) {
+ offset_(offset), option_valid_(true) {
}
-
- /// \brief Constructor, used to create option from buffer iterators
+ /// \brief Copy constructor, creates LocalizedOption from Option6IA.
///
- /// This contructor is similar to the previous one, but it does not take
- /// the whole vector<uint8_t>, but rather subset of it.
+ /// This copy constructor creates regular option from Option6IA.
+ /// The data from Option6IA data members are copied to
+ /// option buffer in appropriate sequence.
///
- /// \param u specifies universe (V4 or V6)
- /// \param type option type (0-255 for V4 and 0-65535 for V6)
- /// \param first iterator to the first element that should be copied
- /// \param last iterator to the next element after the last one
- /// to be copied.
- /// \param offset offset of option in a packet (zero is default)
- LocalizedOption(dhcp::Option::Universe u,
- uint16_t type,
- dhcp::OptionBufferConstIter first,
- dhcp::OptionBufferConstIter last, const size_t offset) :
- dhcp::Option(u, type, first, last),
- offset_(offset) {
+ /// \param opt_ia option to be copied.
+ /// \param offset location of the option in a packet.
+ LocalizedOption(const boost::shared_ptr<dhcp::Option6IA>& opt_ia,
+ const size_t offset) :
+ dhcp::Option(Option::V6, 0, dhcp::OptionBuffer()),
+ offset_(offset), option_valid_(false) {
+ // If given option is NULL we will mark this new option
+ // as invalid. User may query if option is valid when
+ // object is created.
+ if (opt_ia) {
+ // Set universe and type.
+ universe_ = opt_ia->getUniverse();
+ type_ = opt_ia->getType();
+ util::OutputBuffer buf(opt_ia->len() - opt_ia->getHeaderLen());
+ try {
+ // Try to pack option data into the temporary buffer.
+ opt_ia->pack(buf);
+ if (buf.getLength() > 0) {
+ const char* buf_data = static_cast<const char*>(buf.getData());
+ // Option has been packed along with option type flag
+ // and transaction id so we have to skip first 4 bytes
+ // when copying temporary buffer option buffer.
+ data_.assign(buf_data + 4, buf_data + buf.getLength());
+ }
+ option_valid_ = true;
+ } catch (const Exception&) {
+ // If there was an exception somewhere when packing
+ // the data into the buffer we assume that option is
+ // not valid and should not be used.
+ option_valid_ = false;
+ }
+ } else {
+ option_valid_ = false;
+ }
}
/// \brief Returns offset of an option in a DHCP packet.
@@ -112,12 +131,20 @@ public:
/// \return option offset in a packet
size_t getOffset() const { return offset_; };
+ /// \brief Checks if option is valid.
+ ///
+ /// \return true, if option is valid.
+ virtual bool valid() {
+ return (Option::valid() && option_valid_);
+ }
+
private:
size_t offset_; ///< Offset of DHCP option in a packet
+ bool option_valid_; ///< Is option valid.
};
-} // namespace perfdhcp
+} // namespace isc::perfdhcp
} // namespace isc
#endif // __LOCALIZED_OPTION_H
diff --git a/tests/tools/perfdhcp/main.cc b/tests/tools/perfdhcp/main.cc
new file mode 100644
index 0000000..0c706a2
--- /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 <iostream>
+#include <stdint.h>
+
+#include <config.h>
+#include <exceptions/exceptions.h>
+
+#include "test_control.h"
+#include "command_options.h"
+
+using namespace isc::perfdhcp;
+
+int
+main(int argc, char* argv[]) {
+ CommandOptions& command_options = CommandOptions::instance();
+ try {
+ command_options.parse(argc, argv);
+ } catch(isc::Exception& e) {
+ std::cout << "Error parsing command line options: "
+ << e.what() << std::endl;
+ command_options.usage();
+ return(1);
+ }
+ try{
+ TestControl& test_control = TestControl::instance();
+ test_control.run();
+ } catch (isc::Exception& e) {
+ std::cout << "Error running perfdhcp: " << e.what() << std::endl;
+ std::string diags(command_options.getDiags());
+ if (diags.find('e') != std::string::npos) {
+ std::cout << "Fatal error" << std::endl;
+ }
+ return(1);
+ }
+ return(0);
+}
+
diff --git a/tests/tools/perfdhcp/perf_pkt4.cc b/tests/tools/perfdhcp/perf_pkt4.cc
index 3f733af..3ccef94 100644
--- a/tests/tools/perfdhcp/perf_pkt4.cc
+++ b/tests/tools/perfdhcp/perf_pkt4.cc
@@ -16,7 +16,6 @@
#include <dhcp/dhcp6.h>
#include "perf_pkt4.h"
-#include "pkt_transform.h"
using namespace std;
using namespace isc;
@@ -58,5 +57,14 @@ PerfPkt4::rawUnpack() {
return (res);
}
+void
+PerfPkt4::writeAt(size_t dest_pos,
+ std::vector<uint8_t>::iterator first,
+ std::vector<uint8_t>::iterator last) {
+ return (PktTransform::writeAt(data_, dest_pos, first, last));
+}
+
+
+
} // namespace perfdhcp
} // namespace isc
diff --git a/tests/tools/perfdhcp/perf_pkt4.h b/tests/tools/perfdhcp/perf_pkt4.h
index f4cc440..87c7bb0 100644
--- a/tests/tools/perfdhcp/perf_pkt4.h
+++ b/tests/tools/perfdhcp/perf_pkt4.h
@@ -20,6 +20,7 @@
#include <dhcp/pkt4.h>
#include "localized_option.h"
+#include "pkt_transform.h"
namespace isc {
namespace perfdhcp {
@@ -102,11 +103,36 @@ public:
/// \return false If unpack operation failed.
bool rawUnpack();
+ /// \brief Replace contents of buffer with data.
+ ///
+ /// Function replaces part of the buffer with data from vector.
+ ///
+ /// \param dest_pos position in buffer where data is replaced.
+ /// \param first beginning of data range in source vector.
+ /// \param last end of data range in source vector.
+ void writeAt(size_t dest_pos,
+ std::vector<uint8_t>::iterator first,
+ std::vector<uint8_t>::iterator last);
+
+ /// \brief Replace contents of buffer with value.
+ ///
+ /// Function replaces part of buffer with value.
+ ///
+ /// \param dest_pos position in buffer where value is
+ /// to be written.
+ /// \param val value to be written.
+ template<typename T>
+ void writeValueAt(size_t dest_pos, T val) {
+ PktTransform::writeValueAt<T>(data_, dest_pos, val);
+ }
+
private:
size_t transid_offset_; ///< transaction id offset
};
+typedef boost::shared_ptr<PerfPkt4> PerfPkt4Ptr;
+
} // namespace perfdhcp
} // namespace isc
diff --git a/tests/tools/perfdhcp/perf_pkt6.cc b/tests/tools/perfdhcp/perf_pkt6.cc
index 24cfb93..56fe9df 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<uint8_t>::iterator first,
+ std::vector<uint8_t>::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 94fe47b..25fb4e5 100644
--- a/tests/tools/perfdhcp/perf_pkt6.h
+++ b/tests/tools/perfdhcp/perf_pkt6.h
@@ -20,6 +20,7 @@
#include <dhcp/pkt6.h>
#include "localized_option.h"
+#include "pkt_transform.h"
namespace isc {
namespace perfdhcp {
@@ -102,11 +103,36 @@ public:
/// \return false if unpack operation failed.
bool rawUnpack();
+ /// \brief Replace contents of buffer with data.
+ ///
+ /// Function replaces part of the buffer with data from vector.
+ ///
+ /// \param dest_pos position in buffer where data is replaced.
+ /// \param first beginning of data range in source vector.
+ /// \param last end of data range in source vector.
+ void writeAt(size_t dest_pos,
+ std::vector<uint8_t>::iterator first,
+ std::vector<uint8_t>::iterator last);
+
+ /// \brief Replace contents of buffer with value.
+ ///
+ /// Function replaces part of buffer with value.
+ ///
+ /// \param dest_pos position in buffer where value is
+ /// to be written.
+ /// \param val value to be written.
+ template<typename T>
+ void writeValueAt(size_t dest_pos, T val) {
+ PktTransform::writeValueAt<T>(data_, dest_pos, val);
+ }
+
private:
size_t transid_offset_; ///< transaction id offset
};
+typedef boost::shared_ptr<PerfPkt6> PerfPkt6Ptr;
+
} // namespace perfdhcp
} // namespace isc
diff --git a/tests/tools/perfdhcp/pkt_transform.cc b/tests/tools/perfdhcp/pkt_transform.cc
index 5ed39bf..b1c64e2 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 7fb19f4..1f57105 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<uint8_t>::iterator first,
+ std::vector<uint8_t>::iterator last);
+
+ /// \brief Replace contents of one vector with uint16 value.
+ ///
+ /// Function replaces data inside one vector with uint16_t value.
+ ///
+ /// \param in_buffer destination buffer.
+ /// \param dest_pos position in destination buffer.
+ /// \param val value to be written.
+ template<typename T>
+ static void writeValueAt(dhcp::OptionBuffer& in_buffer, size_t dest_pos,
+ T val) {
+ // @todo consider replacing the loop with switch statement
+ // checking sizeof(T).
+ for (int i = 0; i < sizeof(T); ++i) {
+ in_buffer[dest_pos + i] = (val >> 8 * (sizeof(T) - i - 1)) & 0xFF;
+ }
+ }
+
private:
/// \brief Replaces contents of options in a buffer.
///
@@ -131,6 +160,7 @@ private:
/// \throw isc::Unexpected if options unpack failed.
static void unpackOptions(const dhcp::OptionBuffer& in_buffer,
const dhcp::Option::OptionCollection& options);
+
};
} // namespace perfdhcp
diff --git a/tests/tools/perfdhcp/stats_mgr.h b/tests/tools/perfdhcp/stats_mgr.h
index 245c69e..a8dfa8b 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 <class T>
+/// \param T class representing DHCPv4 or DHCPv6 packet.
+template <class T = dhcp::Pkt4>
class StatsMgr : public boost::noncopyable {
public:
@@ -138,7 +138,7 @@ public:
/// \param packet packet which transaction id is to be hashed.
/// \throw isc::BadValue if packet is null.
/// \return transaction id hash.
- static uint32_t hashTransid(const boost::shared_ptr<const T>& packet) {
+ static uint32_t hashTransid(const boost::shared_ptr<T>& packet) {
if (!packet) {
isc_throw(BadValue, "Packet is null");
}
@@ -214,21 +214,33 @@ public:
/// }
/// \endcode
typedef boost::multi_index_container<
- boost::shared_ptr<const T>,
+ // Container holds shared_ptr<Pkt4> or shared_ptr<Pkt6> objects.
+ boost::shared_ptr<T>,
+ // List container indexes.
boost::multi_index::indexed_by<
+ // Sequenced index provides the way to use this container
+ // in the same way as std::list.
boost::multi_index::sequenced<>,
+ // The other index keeps products of transaction id.
boost::multi_index::hashed_non_unique<
- boost::multi_index::global_fun<
- const boost::shared_ptr<const T>&,
- uint32_t,
- &ExchangeStats::hashTransid
- >
+ // Specify hash function to get the product of
+ // transaction id. This product is obtained by calling
+ // hashTransid() function.
+ boost::multi_index::global_fun<
+ // Hashing function takes shared_ptr<Pkt4> or
+ // shared_ptr<Pkt6> as argument.
+ const boost::shared_ptr<T>&,
+ // ... and returns uint32 value.
+ uint32_t,
+ // ... and here is a reference to it.
+ &ExchangeStats::hashTransid
+ >
>
>
> PktList;
/// Packet list iterator for sequencial access to elements.
- typedef typename PktList::const_iterator PktListIterator;
+ typedef typename PktList::iterator PktListIterator;
/// Packet list index to search packets using transaction id hash.
typedef typename PktList::template nth_index<1>::type
PktListTransidHashIndex;
@@ -243,20 +255,21 @@ public:
/// In this mode all packets are stored throughout the test execution.
ExchangeStats(const ExchangeType xchg_type, const bool archive_enabled)
: xchg_type_(xchg_type),
+ sent_packets_(),
+ rcvd_packets_(),
+ archived_packets_(),
+ archive_enabled_(archive_enabled),
min_delay_(std::numeric_limits<double>::max()),
max_delay_(0.),
sum_delay_(0.),
- orphans_(0),
sum_delay_squared_(0.),
- ordered_lookups_(0),
+ orphans_(0),
unordered_lookup_size_sum_(0),
unordered_lookups_(0),
+ ordered_lookups_(0),
sent_packets_num_(0),
- rcvd_packets_num_(0),
- sent_packets_(),
- rcvd_packets_(),
- archived_packets_(),
- archive_enabled_(archive_enabled) {
+ rcvd_packets_num_(0)
+ {
next_sent_ = sent_packets_.begin();
}
@@ -266,7 +279,7 @@ public:
///
/// \param packet packet object to be added.
/// \throw isc::BadValue if packet is null.
- void appendSent(const boost::shared_ptr<const T>& packet) {
+ void appendSent(const boost::shared_ptr<T>& packet) {
if (!packet) {
isc_throw(BadValue, "Packet is null");
}
@@ -280,7 +293,7 @@ public:
///
/// \param packet packet object to be added.
/// \throw isc::BadValue if packet is null.
- void appendRcvd(const boost::shared_ptr<const T>& packet) {
+ void appendRcvd(const boost::shared_ptr<T>& packet) {
if (!packet) {
isc_throw(BadValue, "Packet is null");
}
@@ -296,8 +309,8 @@ public:
/// \param rcvd_packet received packet
/// \throw isc::BadValue if sent or received packet is null.
/// \throw isc::Unexpected if failed to calculate timestamps
- void updateDelays(const boost::shared_ptr<const T>& sent_packet,
- const boost::shared_ptr<const T>& rcvd_packet) {
+ void updateDelays(const boost::shared_ptr<T>& sent_packet,
+ const boost::shared_ptr<T>& rcvd_packet) {
if (!sent_packet) {
isc_throw(BadValue, "Sent packet is null");
}
@@ -355,7 +368,8 @@ public:
/// \throw isc::BadValue if received packet is null.
/// \return packet having specified transaction or NULL if packet
/// not found
- boost::shared_ptr<const T> matchPackets(const boost::shared_ptr<const T>& rcvd_packet) {
+ boost::shared_ptr<T>
+ matchPackets(const boost::shared_ptr<T>& rcvd_packet) {
if (!rcvd_packet) {
isc_throw(BadValue, "Received packet is null");
}
@@ -366,7 +380,7 @@ public:
// that the received packet we got has no corresponding
// sent packet so orphans counter has to be updated.
++orphans_;
- return(boost::shared_ptr<const T>());
+ return(boost::shared_ptr<T>());
} else if (next_sent_ == sent_packets_.end()) {
// Even if there are still many unmatched packets on the
// list we might hit the end of it because of unordered
@@ -425,13 +439,13 @@ public:
// If we are here, it means that both ordered lookup and
// unordered lookup failed. Searched packet is not on the list.
++orphans_;
- return(boost::shared_ptr<const T>());
+ return(boost::shared_ptr<T>());
}
// Packet is matched so we count it. We don't count unmatched packets
// as they are counted as orphans with a separate counter.
++rcvd_packets_num_;
- boost::shared_ptr<const T> sent_packet(*next_sent_);
+ boost::shared_ptr<T> sent_packet(*next_sent_);
// If packet was found, we assume it will be never searched
// again. We want to delete this packet from the list to
// improve performance of future searches.
@@ -548,6 +562,19 @@ public:
/// \return number of received packets.
uint64_t getRcvdPacketsNum() const { return(rcvd_packets_num_); }
+ /// \brief Return number of dropped packets.
+ ///
+ /// Method returns number of dropped packets.
+ ///
+ /// \return number of dropped packets.
+ uint64_t getDroppedPacketsNum() const {
+ uint64_t drops = 0;
+ if (getSentPacketsNum() > getRcvdPacketsNum()) {
+ drops = getSentPacketsNum() - getRcvdPacketsNum();
+ }
+ return(drops);
+ }
+
/// \brief Print main statistics for packet exchange.
///
/// Method prints main statistics for particular exchange.
@@ -555,10 +582,9 @@ public:
/// number of dropped packets and number of orphans.
void printMainStats() const {
using namespace std;
- uint64_t drops = getRcvdPacketsNum() - getSentPacketsNum();
cout << "sent packets: " << getSentPacketsNum() << endl
<< "received packets: " << getRcvdPacketsNum() << endl
- << "drops: " << drops << endl
+ << "drops: " << getDroppedPacketsNum() << endl
<< "orphans: " << getOrphans() << endl;
}
@@ -610,7 +636,7 @@ public:
for (PktListIterator it = rcvd_packets_.begin();
it != rcvd_packets_.end();
++it) {
- boost::shared_ptr<const T> rcvd_packet = *it;
+ boost::shared_ptr<T> rcvd_packet = *it;
PktListTransidHashIndex& idx =
archived_packets_.template get<1>();
std::pair<PktListTransidHashIterator,
@@ -621,7 +647,7 @@ public:
++it) {
if ((*it_archived)->getTransid() ==
rcvd_packet->getTransid()) {
- boost::shared_ptr<const T> sent_packet = *it_archived;
+ boost::shared_ptr<T> sent_packet = *it_archived;
// Get sent and received packet times.
ptime sent_time = sent_packet->getTimestamp();
ptime rcvd_time = rcvd_packet->getTimestamp();
@@ -761,7 +787,8 @@ public:
StatsMgr(const bool archive_enabled = false) :
exchanges_(),
custom_counters_(),
- archive_enabled_(archive_enabled) {
+ archive_enabled_(archive_enabled),
+ boot_time_(boost::posix_time::microsec_clock::universal_time()) {
}
/// \brief Specify new exchange type.
@@ -819,7 +846,7 @@ public:
///
/// \param counter_key key poitinh to the counter in the counters map.
/// \return pointer to specified counter after incrementation.
- const CustomCounter& IncrementCounter(const std::string& counter_key) {
+ const CustomCounter& incrementCounter(const std::string& counter_key) {
CustomCounterPtr counter = getCounter(counter_key);
return(++(*counter));
}
@@ -835,7 +862,7 @@ public:
/// \throw isc::BadValue if invalid exchange type specified or
/// packet is null.
void passSentPacket(const ExchangeType xchg_type,
- const boost::shared_ptr<const T>& packet) {
+ const boost::shared_ptr<T>& packet) {
ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
xchg_stats->appendSent(packet);
}
@@ -853,10 +880,11 @@ public:
/// or packet is null.
/// \throw isc::Unexpected if corresponding packet was not
/// found on the list of sent packets.
- void passRcvdPacket(const ExchangeType xchg_type,
- const boost::shared_ptr<const T>& packet) {
+ boost::shared_ptr<T>
+ passRcvdPacket(const ExchangeType xchg_type,
+ const boost::shared_ptr<T>& packet) {
ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
- boost::shared_ptr<const T> sent_packet
+ boost::shared_ptr<T> sent_packet
= xchg_stats->matchPackets(packet);
if (sent_packet) {
@@ -865,6 +893,7 @@ public:
xchg_stats->appendRcvd(packet);
}
}
+ return(sent_packet);
}
/// \brief Return minumum delay between sent and received packet.
@@ -999,6 +1028,33 @@ public:
return(xchg_stats->getRcvdPacketsNum());
}
+ /// \brief Return total number of dropped packets.
+ ///
+ /// Method returns total number of dropped packets for specified
+ /// exchange type.
+ ///
+ /// \param xchg_type exchange type.
+ /// \throw isc::BadValue if invalid exchange type specified.
+ /// \return number of dropped packets.
+ uint64_t getDroppedPacketsNum(const ExchangeType xchg_type) const {
+ ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
+ return(xchg_stats->getDroppedPacketsNum());
+ }
+
+ /// \brief Get time period since the start of test.
+ ///
+ /// Calculate dna return period since the test start. This
+ /// can be specifically helpful when calculating packet
+ /// exchange rates.
+ ///
+ /// \return test period so far.
+ boost::posix_time::time_period getTestPeriod() const {
+ using namespace boost::posix_time;
+ time_period test_period(boot_time_,
+ microsec_clock::universal_time());
+ return test_period;
+ }
+
/// \brief Return name of the exchange.
///
/// Method returns name of the specified exchange type.
@@ -1052,6 +1108,32 @@ public:
}
}
+ /// \brief Print intermediate statistics.
+ ///
+ /// Method prints intermediate statistics for all exchanges.
+ /// Statistics includes sent, received and dropped packets
+ /// counters.
+ void printIntermediateStats() const {
+ std::ostringstream stream_sent;
+ std::ostringstream stream_rcvd;
+ std::ostringstream stream_drops;
+ std::string sep("");
+ for (ExchangesMapIterator it = exchanges_.begin();
+ it != exchanges_.end(); ++it) {
+
+ if (it != exchanges_.begin()) {
+ sep = "/";
+ }
+ stream_sent << sep << it->second->getSentPacketsNum();
+ stream_rcvd << sep << it->second->getRcvdPacketsNum();
+ stream_drops << sep << it->second->getDroppedPacketsNum();
+ }
+ std::cout << "sent: " << stream_sent.str()
+ << "; received: " << stream_rcvd.str()
+ << "; drops: " << stream_drops.str()
+ << std::endl;
+ }
+
/// \brief Print timestamps of all packets.
///
/// Method prints timestamps of all sent and received
@@ -1129,6 +1211,8 @@ private:
/// for extended period of time and many packets have to be
/// archived.
bool archive_enabled_;
+
+ boost::posix_time::ptime boot_time_; ///< Time when test is started.
};
} // namespace perfdhcp
diff --git a/tests/tools/perfdhcp/templates/Makefile.am b/tests/tools/perfdhcp/templates/Makefile.am
new file mode 100644
index 0000000..4da2027
--- /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 0000000..9a6e5ea
--- /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 0000000..32447d6
--- /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 0000000..1e3e76f
--- /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 0000000..41c5ad3
--- /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 0000000..c154cf9
--- /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 <fstream>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <exceptions/exceptions.h>
+#include <asiolink/io_address.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/option6_ia.h>
+#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<uint8_t>
+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<uint8_t> 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<uint8_t>::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<uint8_t>
+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<uint8_t> duid(options.getDuidTemplate());
+ // @todo: add support for DUIDs of different sizes.
+ std::vector<uint8_t> 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<class T>
+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<uint64_t>(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<StatsMgr6::ExchangeType>(xchg_type)));
+}
+
+uint64_t
+TestControl::getSentPacketsNum(const ExchangeType xchg_type) const {
+ uint8_t ip_version = CommandOptions::instance().getIpVersion();
+ if (ip_version == 4) {
+ return (stats_mgr4_->getSentPacketsNum(xchg_type));
+ }
+ return (stats_mgr6_->
+ getSentPacketsNum(static_cast<StatsMgr6::ExchangeType>(xchg_type)));
+}
+
+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<std::string> template_files = options.getTemplateFiles();
+ for (std::vector<std::string>::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<uint8_t>& vec,
+ const std::string& separator /* ="" */) const {
+ std::ostringstream stream;
+ for (std::vector<uint8_t>::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<char> hex_digits(temp_size);
+ std::vector<uint8_t> 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<uint8_t>(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<uint8_t> 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<uint8_t>& 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<uint8_t> 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<uint8_t> 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>(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>(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>(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<uint32_t>(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<uint8_t> mac_address(chaddr, chaddr + HW_ETHER_LEN);
+ pkt4->setHWAddr(HTYPE_ETHER, mac_address.size(), mac_address);
+ // Set elapsed time.
+ uint32_t elapsed_time = getElapsedTime<Pkt4Ptr>(discover_pkt4, offer_pkt4);
+ pkt4->setSecs(static_cast<uint16_t>(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<uint8_t>& 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<uint8_t> 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<uint8_t> 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<Pkt4Ptr>(discover_pkt4, offer_pkt4);
+ pkt4->writeValueAt<uint16_t>(elp_offset,
+ static_cast<uint16_t>(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<LocalizedOption>
+ 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<LocalizedOption>
+ 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<LocalizedOption>
+ 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<uint32_t>(yiaddr));
+ pkt4->addOption(opt_requested_ip);
+
+ setDefaults4(socket, boost::static_pointer_cast<Pkt4>(pkt4));
+ // Prepare on-wire data.
+ pkt4->rawPack();
+ IfaceMgr::instance().send(boost::static_pointer_cast<Pkt4>(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>(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<uint8_t>& 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<LocalizedOption>
+ 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<LocalizedOption>
+ 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<LocalizedOption>
+ 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<Option6IA> opt_ia_na_advertise =
+ boost::static_pointer_cast<Option6IA>(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<LocalizedOption>
+ 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<LocalizedOption>
+ 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<LocalizedOption>
+ 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<uint8_t> 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<uint8_t>& 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<uint8_t> 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 0000000..1b0b83c
--- /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 <string>
+#include <vector>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/function.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <dhcp/iface_mgr.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+
+#include "stats_mgr.h"
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief Test Control class.
+///
+/// This class is responsible for executing DHCP performance
+/// test end to end.
+///
+/// Option factory functions are registered using
+/// \ref dhcp::LibDHCP::OptionFactoryRegister. Registered factory functions
+/// provide a way to create options of the same type in the same way.
+/// When new option instance is needed the corresponding factory
+/// function is called to create it. This is done by calling
+/// \ref dhcp::Option::factory with DHCP message type specified as one of
+/// parameters. Some of the parameters passed to factory function
+/// may be ignored (e.g. option buffer).
+/// Please note that naming convention for factory functions within this
+/// class is as follows:
+/// - factoryABC4 - factory function for DHCPv4 option,
+/// - factoryDEF6 - factory function for DHCPv6 option,
+/// - factoryGHI - factory function that can be used to create either
+/// DHCPv4 or DHCPv6 option.
+class TestControl : public boost::noncopyable {
+public:
+
+ /// Default transaction id offset.
+ static const size_t DHCPV4_TRANSID_OFFSET = 4;
+ /// Default offset of MAC's last octet.
+ static const size_t DHCPV4_RANDOMIZATION_OFFSET = 35;
+ /// Default elapsed time offset.
+ static const size_t DHCPV4_ELAPSED_TIME_OFFSET = 8;
+ /// Default server id offset.
+ static const size_t DHCPV4_SERVERID_OFFSET = 54;
+ /// Default requested ip offset.
+ static const size_t DHCPV4_REQUESTED_IP_OFFSET = 240;
+ /// Default DHCPV6 transaction id offset.
+ static const size_t DHCPV6_TRANSID_OFFSET = 1;
+ /// Default DHCPV6 randomization offset (last octet of DUID)
+ static const size_t DHCPV6_RANDOMIZATION_OFFSET = 21;
+ /// Default DHCPV6 elapsed time offset.
+ static const size_t DHCPV6_ELAPSED_TIME_OFFSET = 84;
+ /// Default DHCPV6 server id offset.
+ static const size_t DHCPV6_SERVERID_OFFSET = 22;
+ /// Default DHCPV6 IA_NA offset.
+ static const size_t DHCPV6_IA_NA_OFFSET = 40;
+
+ /// Statistics Manager for DHCPv4.
+ typedef StatsMgr<dhcp::Pkt4> StatsMgr4;
+ /// Pointer to Statistics Manager for DHCPv4;
+ typedef boost::shared_ptr<StatsMgr4> StatsMgr4Ptr;
+ /// Statictics Manager for DHCPv6.
+ typedef StatsMgr<dhcp::Pkt6> StatsMgr6;
+ /// Pointer to Statistics Manager for DHCPv6.
+ typedef boost::shared_ptr<StatsMgr6> StatsMgr6Ptr;
+ /// Packet exchange type.
+ typedef StatsMgr<>::ExchangeType ExchangeType;
+ /// Packet template buffer.
+ typedef std::vector<uint8_t> TemplateBuffer;
+ /// Packet template buffers list.
+ typedef std::vector<TemplateBuffer> TemplateBufferCollection;
+
+ /// \brief Socket wrapper structure.
+ ///
+ /// This is the wrapper that holds descriptor of the socket
+ /// used to run DHCP test. The wrapped socket is closed in
+ /// the destructor. This prevents resource leaks when when
+ /// function that created the socket ends (normally or
+ /// when exception occurs). This structure extends parent
+ /// structure with new field ifindex_ that holds interface
+ /// index where socket is bound to.
+ struct TestControlSocket : public dhcp::IfaceMgr::SocketInfo {
+ /// Interface index.
+ uint16_t ifindex_;
+ /// Is socket valid. It will not be valid if the provided socket
+ /// descriptor does not point to valid socket.
+ bool valid_;
+
+ /// \brief Constructor of socket wrapper class.
+ ///
+ /// This constructor uses provided socket descriptor to
+ /// find the name of the interface where socket has been
+ /// bound to. If provided socket descriptor is invalid then
+ /// valid_ field is set to false;
+ ///
+ /// \param socket socket descriptor.
+ TestControlSocket(const int socket);
+
+ /// \brief Destriuctor of the socket wrapper class.
+ ///
+ /// Destructor closes wrapped socket.
+ ~TestControlSocket();
+
+ private:
+ /// \brief Initialize socket data.
+ ///
+ /// This method initializes members of the class that Interface
+ /// Manager holds: interface name, local address.
+ ///
+ /// \throw isc::BadValue if interface for specified socket
+ /// descriptor does not exist.
+ void initSocketData();
+ };
+
+ /// \brief Number generator class.
+ ///
+ /// This is default numbers generator class. The member function is
+ /// used to generate uint32_t values. Other generator classes should
+ /// derive from this one to implement generation algorithms
+ /// (e.g. sequencial or based on random function).
+ class NumberGenerator {
+ public:
+ /// \brief Generate number.
+ ///
+ /// \return Generate number.
+ virtual uint32_t generate() = 0;
+ };
+
+ /// The default generator pointer.
+ typedef boost::shared_ptr<NumberGenerator> NumberGeneratorPtr;
+
+ /// \brief Sequencial numbers generatorc class.
+ class SequencialGenerator : public NumberGenerator {
+ public:
+ /// \brief Constructor.
+ ///
+ /// \param range maximum number generated. If 0 is given then
+ /// range defaults to maximym uint32_t value.
+ SequencialGenerator(uint32_t range = 0xFFFFFFFF) :
+ NumberGenerator(),
+ num_(0),
+ range_(range) {
+ if (range_ == 0) {
+ range_ = 0xFFFFFFFF;
+ }
+ }
+
+ /// \brief Generate number sequencialy.
+ ///
+ /// \return generated number.
+ virtual uint32_t generate() {
+ uint32_t num = num_;
+ num_ = (num_ + 1) % range_;
+ return (num);
+ }
+ private:
+ uint32_t num_; ///< Current number.
+ uint32_t range_; ///< Maximum number generated.
+ };
+
+ /// \brief Length of the Ethernet HW address (MAC) in bytes.
+ ///
+ /// \todo Make this variable length as there are cases when HW
+ /// address is longer than this (e.g. 20 bytes).
+ static const uint8_t HW_ETHER_LEN = 6;
+
+ /// TestControl is a singleton class. This method returns reference
+ /// to its sole instance.
+ ///
+ /// \return the only existing instance of test control
+ static TestControl& instance();
+
+ /// brief\ Run performance test.
+ ///
+ /// Method runs whole performance test. Command line options must
+ /// be parsed prior to running this function. Othewise function will
+ /// throw exception.
+ ///
+ /// \throw isc::InvalidOperation if command line options are not parsed.
+ /// \throw isc::Unexpected if internal Test Controler error occured.
+ void run();
+
+ /// \brief Set new transaction id generator.
+ ///
+ /// \param generator generator object to be used.
+ void setTransidGenerator(const NumberGeneratorPtr& generator) {
+ transid_gen_.reset();
+ transid_gen_ = generator;
+ }
+
+ /// \brief Set new MAC address generator.
+ ///
+ /// Set numbers generator that will be used to generate various
+ /// MAC addresses to simulate number of clients.
+ ///
+ /// \param generator object to be used.
+ void setMacAddrGenerator(const NumberGeneratorPtr& generator) {
+ macaddr_gen_.reset();
+ macaddr_gen_ = generator;
+ }
+
+ // We would really like following methods and members to be private but
+ // they have to be accessible for unit-testing. Another, possibly better,
+ // solution is to make this class friend of test class but this is not
+ // what's followed in other classes.
+protected:
+ /// \brief Default constructor.
+ ///
+ /// Default constructor is protected as the object can be created
+ /// only via \ref instance method.
+ TestControl();
+
+ /// \brief Check if test exit condtitions fulfilled.
+ ///
+ /// Method checks if the test exit conditions are fulfiled.
+ /// Exit conditions are checked periodically from the
+ /// main loop. Program should break the main loop when
+ /// this method returns true. It is calling function
+ /// responsibility to break main loop gracefully and
+ /// cleanup after test execution.
+ ///
+ /// \return true if any of the exit conditions is fulfiled.
+ bool checkExitConditions() const;
+
+ /// \brief Factory function to create DHCPv6 ELAPSED_TIME option.
+ ///
+ /// This factory function creates DHCPv6 ELAPSED_TIME option instance.
+ /// If empty buffer is passed the option buffer will be initialized
+ /// to length 2 and values will be initialized to zeros. Otherwise
+ /// function will initialize option buffer with values in passed buffer.
+ ///
+ /// \param u universe (ignored)
+ /// \param type option-type (ignored).
+ /// \param buf option-buffer containing option content (2 bytes) or
+ /// empty buffer if option content has to be set to default (0) value.
+ /// \throw if elapsed time buffer size is neither 2 nor 0.
+ /// \return instance o the option.
+ static dhcp::OptionPtr
+ factoryElapsedTime6(dhcp::Option::Universe u,
+ uint16_t type,
+ const dhcp::OptionBuffer& buf);
+
+ /// \brief Factory function to create generic option.
+ ///
+ /// This factory function creates option with specified universe,
+ /// type and buf. It does not have any additional logic validating
+ /// the buffer contents, size etc.
+ ///
+ /// \param u universe (V6 or V4).
+ /// \param type option-type (ignored).
+ /// \param buf option-buffer.
+ /// \return instance o the option.
+ static dhcp::OptionPtr factoryGeneric(dhcp::Option::Universe u,
+ uint16_t type,
+ const dhcp::OptionBuffer& buf);
+
+ /// \brief Factory function to create IA_NA option.
+ ///
+ /// This factory function creates DHCPv6 IA_NA option instance.
+ ///
+ /// \todo add support for IA Address options.
+ ///
+ /// \param u universe (ignored).
+ /// \param type option-type (ignored).
+ /// \param buf option-buffer carrying IANA suboptions.
+ /// \return instance of IA_NA option.
+ static dhcp::OptionPtr factoryIana6(dhcp::Option::Universe u,
+ uint16_t type,
+ const dhcp::OptionBuffer& buf);
+
+ /// \brief Factory function to create DHCPv6 ORO option.
+ ///
+ /// This factory function creates DHCPv6 Option Request Option instance.
+ /// The created option will contain the following set of requested options:
+ /// - D6O_NAME_SERVERS
+ /// - D6O_DOMAIN_SEARCH
+ ///
+ /// \param u universe (ignored).
+ /// \param type option-type (ignored).
+ /// \param buf option-buffer (ignored).
+ /// \return instance of ORO option.
+ static dhcp::OptionPtr
+ factoryOptionRequestOption6(dhcp::Option::Universe u,
+ uint16_t type,
+ const dhcp::OptionBuffer& buf);
+
+ /// \brief Factory function to create DHCPv6 RAPID_COMMIT option instance.
+ ///
+ /// This factory function creates DHCPv6 RAPID_COMMIT option instance.
+ /// The buffer passed to this option must be empty because option does
+ /// not have any payload.
+ ///
+ /// \param u universe (ignored).
+ /// \param type option-type (ignored).
+ /// \param buf option-buffer (ignored).
+ /// \return instance of RAPID_COMMIT option..
+ static dhcp::OptionPtr factoryRapidCommit6(dhcp::Option::Universe u,
+ uint16_t type,
+ const dhcp::OptionBuffer& buf);
+
+
+ /// \brief Factory function to create DHCPv4 Request List option.
+ ///
+ /// This factory function creayes DHCPv4 PARAMETER_REQUEST_LIST option
+ /// instance with the following set of requested options:
+ /// - DHO_SUBNET_MASK,
+ /// - DHO_BROADCAST_ADDRESS,
+ /// - DHO_TIME_OFFSET,
+ /// - DHO_ROUTERS,
+ /// - DHO_DOMAIN_NAME,
+ /// - DHO_DOMAIN_NAME_SERVERS,
+ /// - DHO_HOST_NAME.
+ ///
+ /// \param u universe (ignored).
+ /// \param type option-type (ignored).
+ /// \param buf option-buffer (ignored).
+ /// \return instance o the generic option.
+ static dhcp::OptionPtr factoryRequestList4(dhcp::Option::Universe u,
+ uint16_t type,
+ const dhcp::OptionBuffer& buf);
+
+ /// \brief Generate DUID.
+ ///
+ /// Method generates unique DUID. The number of DUIDs it can generate
+ /// depends on the number of simulated clients, which is specified
+ /// from the command line. It uses \ref CommandOptions object to retrieve
+ /// number of clients. Since the last six octets of DUID are constructed
+ /// from the MAC address, this function uses \ref generateMacAddress
+ /// internally to randomize the DUID.
+ ///
+ /// \todo add support for other types of DUID.
+ ///
+ /// \param [out] randomized number of bytes randomized (initial value
+ /// is ignored).
+ /// \throw isc::BadValue if \ref generateMacAddress throws.
+ /// \return vector representing DUID.
+ std::vector<uint8_t> generateDuid(uint8_t& randomized) const;
+
+ /// \brief Generate MAC address.
+ ///
+ /// This method generates MAC address. The number of unique
+ /// MAC addresses it can generate is determined by the number
+ /// simulated DHCP clients specified from command line. It uses
+ /// \ref CommandOptions object to retrieve number of clients.
+ /// Based on this the random value is generated and added to
+ /// the MAC address template (default MAC address).
+ ///
+ /// \param [out] randomized number of bytes randomized (initial
+ /// value is ignored).
+ /// \throw isc::BadValue if MAC address template (default or specified
+ /// from the command line) has invalid size (expected 6 octets).
+ /// \return generated MAC address.
+ std::vector<uint8_t> generateMacAddress(uint8_t& randomized) const;
+
+ /// \brief generate transaction id.
+ ///
+ /// Generate transaction id value (32-bit for DHCPv4,
+ /// 24-bit for DHCPv6).
+ ///
+ /// \return generated transaction id.
+ uint32_t generateTransid() {
+ return (transid_gen_->generate());
+ }
+
+ /// \brief Returns number of exchanges to be started.
+ ///
+ /// Method returns number of new exchanges to be started as soon
+ /// as possible to satisfy expected rate. Calculation used here
+ /// is based on current time, due time calculated with
+ /// \ref updateSendDue function and expected rate.
+ ///
+ /// \return number of exchanges to be started immediately.
+ uint64_t getNextExchangesNum() const;
+
+ /// \brief Return template buffer.
+ ///
+ /// Method returns template buffer at specified index.
+ ///
+ /// \param idx index of template buffer.
+ /// \throw isc::OutOfRange if buffer index out of bounds.
+ /// \return reference to template buffer.
+ TemplateBuffer getTemplateBuffer(const size_t idx) const;
+
+ /// \brief Reads packet templates from files.
+ ///
+ /// Method iterates through all specified template files, reads
+ /// their content and stores it in class internal buffers. Template
+ /// file names are specified from the command line with -T option.
+ ///
+ /// \throw isc::BadValue if any of the template files does not exist
+ void initPacketTemplates();
+
+ /// \brief Initializes Statistics Manager.
+ ///
+ /// This function initializes Statistics Manager. If there is
+ /// the one initialized already it is released.
+ void initializeStatsMgr();
+
+ /// \brief Open socket to communicate with DHCP server.
+ ///
+ /// Method opens socket and binds it to local address. Function will
+ /// use either interface name, local address or server address
+ /// to create a socket, depending on what is available (specified
+ /// from the command line). If socket can't be created for any
+ /// reason, exception is thrown.
+ /// If destination address is broadcast (for DHCPv4) or multicast
+ /// (for DHCPv6) than broadcast or multicast option is set on
+ /// the socket. Opened socket is registered and managed by IfaceMgr.
+ ///
+ /// \throw isc::BadValue if socket can't be created for given
+ /// interface, local address or remote address.
+ /// \throw isc::InvalidOperation if broadcast option can't be
+ /// set for the v4 socket or if multicast option cat't be set
+ /// for the v6 socket.
+ /// \throw isc::Unexpected if interal unexpected error occured.
+ /// \return socket descriptor.
+ int openSocket() const;
+
+ /// \brief Print intermediate statistics.
+ ///
+ /// Print brief statistics regarding number of sent packets,
+ /// received packets and dropped packets so far.
+ void printIntermediateStats();
+
+ /// \brief Print rate statistics.
+ ///
+ /// Method print packet exchange rate statistics.
+ void printRate() const;
+
+ /// \brief Print performance statistics.
+ ///
+ /// Method prints performance statistics.
+ /// \throws isc::InvalidOperation if Statistics Manager was
+ /// not initialized.
+ void printStats() const;
+
+ /// \brief Process received DHCPv4 packet.
+ ///
+ /// Method performs processing of the received DHCPv4 packet,
+ /// updates statistics and responds to the server if required,
+ /// e.g. when OFFER packet arrives, this function will initiate
+ /// REQUEST message to the server.
+ ///
+ /// \warning this method does not check if provided socket is
+ /// valid (specifically if v4 socket for received v4 packet).
+ ///
+ /// \param [in] socket socket to be used.
+ /// \param [in] pkt4 object representing DHCPv4 packet received.
+ /// \throw isc::BadValue if unknown message type received.
+ /// \throw isc::Unexpected if unexpected error occured.
+ void processReceivedPacket4(const TestControlSocket& socket,
+ const dhcp::Pkt4Ptr& pkt4);
+
+ /// \brief Process received DHCPv6 packet.
+ ///
+ /// Method performs processing of the received DHCPv6 packet,
+ /// updates statistics and responsds to the server if required,
+ /// e.g. when ADVERTISE packet arrives, this function will initiate
+ /// REQUEST message to the server.
+ ///
+ /// \warning this method does not check if provided socket is
+ /// valid (specifically if v4 socket for received v4 packet).
+ ///
+ /// \param [in] socket socket to be used.
+ /// \param [in] pkt6 object representing DHCPv6 packet received.
+ /// \throw isc::BadValue if unknown message type received.
+ /// \throw isc::Unexpected if unexpected error occured.
+ void processReceivedPacket6(const TestControlSocket& socket,
+ const dhcp::Pkt6Ptr& pkt6);
+
+ /// \brief Receive DHCPv4 or DHCPv6 packets from the server.
+ ///
+ /// Method receives DHCPv4 or DHCPv6 packets from the server.
+ /// This function will call \ref receivePacket4 or
+ /// \ref receivePacket6 depending if DHCPv4 or DHCPv6 packet
+ /// has arrived.
+ ///
+ /// \warning this method does not check if provided socket is
+ /// valid. Ensure that it is valid prior to calling it.
+ ///
+ /// \param socket socket to be used.
+ /// \throw isc::BadValue if unknown message type received.
+ /// \throw isc::Unexpected if unexpected error occured.
+ void receivePackets(const TestControlSocket& socket);
+
+ /// \brief Register option factory functions for DHCPv4
+ ///
+ /// Method registers option factory functions for DHCPv4.
+ /// These functions are called to create instances of DHCPv4
+ /// options. Call \ref dhcp::Option::factory to invoke factory
+ /// function for particular option. Don't use this function directly.
+ /// Use \ref registerOptionFactories instead.
+ void registerOptionFactories4() const;
+
+ /// \brief Register option factory functions for DHCPv6
+ ///
+ /// Method registers option factory functions for DHCPv6.
+ /// These functions are called to create instances of DHCPv6
+ /// options. Call \ref dhcp::Option::factory to invoke factory
+ /// function for particular option. Don't use this function directly.
+ /// Use \ref registerOptionFactories instead.
+ void registerOptionFactories6() const;
+
+ /// \brief Register option factory functions for DHCPv4 or DHCPv6.
+ ///
+ /// Method registers option factory functions for DHCPv4 or DHCPv6,
+ /// depending in whch mode test is currently running.
+ void registerOptionFactories() const;
+
+
+ /// \brief Resets internal state of the object.
+ ///
+ /// Method resets internal state of the object. It has to be
+ /// called before new test is started.
+ void reset();
+
+ /// \brief Send DHCPv4 DISCOVER message.
+ ///
+ /// Method creates and sends DHCPv4 DISCOVER message to the server
+ /// with the following options:
+ /// - MESSAGE_TYPE set to DHCPDISCOVER
+ /// - PARAMETER_REQUEST_LIST with the same list of requested options
+ /// as described in \ref factoryRequestList4.
+ /// The transaction id and MAC address are randomly generated for
+ /// the message. Range of unique MAC addresses generated depends
+ /// on the number of clients specified from the command line.
+ /// Copy of sent packet is stored in the stats_mgr4_ object to
+ /// update statistics.
+ ///
+ /// \param socket socket to be used to send the message.
+ /// \param preload preload mode, packets not included in statistics.
+ /// \throw isc::Unexpected if failed to create new packet instance.
+ /// \throw isc::BadValue if MAC address has invalid length.
+ void sendDiscover4(const TestControlSocket& socket,
+ const bool preload = false);
+
+ /// \brief Send DHCPv4 DISCOVER message from template.
+ ///
+ /// Method sends DHCPv4 DISCOVER message from template. The
+ /// template data is exepcted to be in binary format. Provided
+ /// buffer is copied and parts of it are replaced with actual
+ /// data (e.g. MAC address, transaction id etc.).
+ /// Copy of sent packet is stored in the stats_mgr4_ object to
+ /// update statistics.
+ ///
+ /// \param socket socket to be used to send the message.
+ /// \param template_buf buffer holding template packet.
+ /// \param preload preload mode, packets not included in statistics.
+ /// \throw isc::OutOfRange if randomization offset is out of bounds.
+ void sendDiscover4(const TestControlSocket& socket,
+ const std::vector<uint8_t>& template_buf,
+ const bool preload = false);
+
+ /// \brief Send DHCPv4 REQUEST message.
+ ///
+ /// Method creates and sends DHCPv4 REQUEST message to the server.
+ /// Copy of sent packet is stored in the stats_mgr4_ object to
+ /// update statistics.
+ ///
+ /// \param socket socket to be used to send message.
+ /// \param discover_pkt4 DISCOVER packet sent.
+ /// \param offer_pkt4 OFFER packet object.
+ /// \throw isc::Unexpected if unexpected error occured.
+ /// \throw isc::InvalidOperation if Statistics Manager has not been
+ /// initialized.
+ void sendRequest4(const TestControlSocket& socket,
+ const dhcp::Pkt4Ptr& discover_pkt4,
+ const dhcp::Pkt4Ptr& offer_pkt4);
+
+ /// \brief Send DHCPv4 REQUEST message from template.
+ ///
+ /// Method sends DHCPv4 REQUEST message from template.
+ /// Copy of sent packet is stored in the stats_mgr4_ object to
+ /// update statistics.
+ ///
+ /// \param socket socket to be used to send message.
+ /// \param template_buf buffer holding template packet.
+ /// \param discover_pkt4 DISCOVER packet sent.
+ /// \param offer_pkt4 OFFER packet received.
+ void sendRequest4(const TestControlSocket& socket,
+ const std::vector<uint8_t>& template_buf,
+ const dhcp::Pkt4Ptr& discover_pkt4,
+ const dhcp::Pkt4Ptr& offer_pkt4);
+
+ /// \brief Send DHCPv6 REQUEST message.
+ ///
+ /// Method creates and sends DHCPv6 REQUEST message to the server
+ /// with the following options:
+ /// - D6O_ELAPSED_TIME
+ /// - D6O_CLIENTID
+ /// - D6O_SERVERID
+ /// Copy of sent packet is stored in the stats_mgr6_ object to
+ /// update statistics.
+ ///
+ /// \param socket socket to be used to send message.
+ /// \param advertise_pkt6 ADVERTISE packet object.
+ /// \throw isc::Unexpected if unexpected error occured.
+ /// \throw isc::InvalidOperation if Statistics Manager has not been
+ /// initialized.
+ void sendRequest6(const TestControlSocket& socket,
+ const dhcp::Pkt6Ptr& advertise_pkt6);
+
+ /// \brief Send DHCPv6 REQUEST message from template.
+ ///
+ /// Method sends DHCPv6 REQUEST message from template.
+ /// Copy of sent packet is stored in the stats_mgr6_ object to
+ /// update statistics.
+ ///
+ /// \param socket socket to be used to send message.
+ /// \param template_buf packet template buffer.
+ /// \param advertise_pkt6 ADVERTISE packet object.
+ void sendRequest6(const TestControlSocket& socket,
+ const std::vector<uint8_t>& template_buf,
+ const dhcp::Pkt6Ptr& advertise_pkt6);
+
+ /// \brief Send DHCPv6 SOLICIT message.
+ ///
+ /// Method creates and sends DHCPv6 SOLICIT message to the server
+ /// with the following options:
+ /// - D6O_ELAPSED_TIME,
+ /// - D6O_RAPID_COMMIT if rapid commit is requested in command line,
+ /// - D6O_CLIENTID,
+ /// - D6O_ORO (Option Request Option),
+ /// - D6O_IA_NA.
+ /// Copy of sent packet is stored in the stats_mgr6_ object to
+ /// update statistics.
+ ///
+ /// \param socket socket to be used to send the message.
+ /// \param preload mode, packets not included in statistics.
+ /// \throw isc::Unexpected if failed to create new packet instance.
+ void sendSolicit6(const TestControlSocket& socket,
+ const bool preload = false);
+
+ /// \brief Send DHCPv6 SOLICIT message from template.
+ ///
+ /// Method sends DHCPv6 SOLICIT message from template.
+ /// Copy of sent packet is stored in the stats_mgr6_ object to
+ /// update statistics.
+ ///
+ /// \param socket socket to be used to send the message.
+ /// \param template_buf packet template buffer.
+ /// \param preload mode, packets not included in statistics.
+ void sendSolicit6(const TestControlSocket& socket,
+ const std::vector<uint8_t>& template_buf,
+ const bool preload = false);
+
+ /// \brief Set default DHCPv4 packet parameters.
+ ///
+ /// This method sets default parameters on the DHCPv4 packet:
+ /// - interface name,
+ /// - local port = 68 (DHCP client port),
+ /// - remote port = 67 (DHCP server port),
+ /// - server's address,
+ /// - GIADDR = local address where socket is bound to,
+ /// - hops = 1 (pretending that we are a relay)
+ ///
+ /// \param socket socket used to send the packet.
+ /// \param pkt reference to packet to be configured.
+ void setDefaults4(const TestControlSocket& socket,
+ const dhcp::Pkt4Ptr& pkt);
+
+ /// \brief Set default DHCPv6 packet parameters.
+ ///
+ /// This method sets default parameters on the DHCPv6 packet:
+ /// - interface name,
+ /// - interface index,
+ /// - local port,
+ /// - remote port,
+ /// - local address,
+ /// - remote address (server).
+ ///
+ /// \param socket socket used to send the packet.
+ /// \param pkt reference to packet to be configured.
+ void setDefaults6(const TestControlSocket& socket,
+ const dhcp::Pkt6Ptr& pkt);
+
+ /// \brief Find if diagnostic flag has been set.
+ ///
+ /// \param diag diagnostic flag (a,e,i,s,r,t,T).
+ /// \return true if diagnostics flag has been set.
+ bool testDiags(const char diag) const;
+
+ /// \brief Update due time to initiate next chunk of exchanges.
+ ///
+ /// Method updates due time to initiate next chunk of exchanges.
+ /// Function takes current time, last sent packet's time and
+ /// expected rate in its calculations.
+ void updateSendDue();
+
+private:
+
+ /// \brief Convert binary value to hex string.
+ ///
+ /// \todo Consider moving this function to src/lib/util.
+ ///
+ /// \param b byte to convert.
+ /// \return hex string.
+ std::string byte2Hex(const uint8_t b) const;
+
+ /// \brief Calculate elapsed time between two packets.
+ ///
+ /// \param T Pkt4Ptr or Pkt6Ptr class.
+ /// \param pkt1 first packet.
+ /// \param pkt2 second packet.
+ /// \throw InvalidOperation if packet timestamps are invalid.
+ /// \return elapsed time in milliseconds between pkt1 and pkt2.
+ template<class T>
+ uint32_t getElapsedTime(const T& pkt1, const T& pkt2);
+
+ /// \brief Get number of received packets.
+ ///
+ /// Get the number of received packets from the Statistics Manager.
+ /// Function may throw if Statistics Manager object is not
+ /// initialized.
+ /// \param xchg_type packet exchange type.
+ /// \return number of received packets.
+ uint64_t getRcvdPacketsNum(const ExchangeType xchg_type) const;
+
+ /// \brief Get number of sent packets.
+ ///
+ /// Get the number of sent packets from the Statistics Manager.
+ /// Function may throw if Statistics Manager object is not
+ /// initialized.
+ /// \param xchg_type packet exchange type.
+ /// \return number of sent packets.
+ uint64_t getSentPacketsNum(const ExchangeType xchg_type) const;
+
+ /// \brief Handle interrupt signal.
+ ///
+ /// Function sets flag indicating that program has been
+ /// interupted.
+ ///
+ /// \param sig signal (ignored)
+ static void handleInterrupt(int sig);
+
+ /// \brief Print main diagnostics data.
+ ///
+ /// Method prints main diagnostics data.
+ void printDiagnostics() const;
+
+ /// \brief Read DHCP message template from file.
+ ///
+ /// Method reads DHCP message template from file and
+ /// converts it to binary format. Read data is appended
+ /// to template_buffers_ vector.
+ void readPacketTemplate(const std::string& file_name);
+
+ /// \brief Convert vector in hexadecimal string.
+ ///
+ /// \todo Consider moving this function to src/lib/util.
+ ///
+ /// \param vec vector to be converted.
+ /// \param separator separator.
+ std::string vector2Hex(const std::vector<uint8_t>& vec,
+ const std::string& separator = "") const;
+
+ boost::posix_time::ptime send_due_; ///< Due time to initiate next chunk
+ ///< of exchanges.
+ boost::posix_time::ptime last_sent_; ///< Indicates when the last exchange
+ /// was initiated.
+
+ boost::posix_time::ptime last_report_; ///< Last intermediate report time.
+
+ StatsMgr4Ptr stats_mgr4_; ///< Statistics Manager 4.
+ StatsMgr6Ptr stats_mgr6_; ///< Statistics Manager 6.
+
+ NumberGeneratorPtr transid_gen_; ///< Transaction id generator.
+ NumberGeneratorPtr macaddr_gen_; ///< Numbers generator for MAC address.
+
+ /// Buffer holiding server id received in first packet
+ dhcp::OptionBuffer first_packet_serverid_;
+
+ /// Packet template buffers.
+ TemplateBufferCollection template_buffers_;
+
+ static bool interrupted_; ///< Is program interrupted.
+};
+
+} // namespace perfdhcp
+} // namespace isc
+
+#endif // __COMMAND_OPTIONS_H
diff --git a/tests/tools/perfdhcp/tests/Makefile.am b/tests/tools/perfdhcp/tests/Makefile.am
index 16e1842..73ec6ba 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 0000000..860a040
--- /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 <string>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+#include "../command_options.h"
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief Command Options Helper class.
+///
+/// This helper class can be shared between unit tests that
+/// need to initialize CommandOptions objects and feed it with
+/// specific command line. The command line can be given as a
+/// string representing program name, options and arguments.
+/// The static method exposed by this class can be used to
+/// tokenize this string into array of C-strings that are later
+/// consumed by \ref CommandOptions::parse. The state of the
+/// CommandOptions object is reset every time the process
+/// function is invoked. Also, when command line parsing is
+/// ended the array of C-string is freed from the memory.
+class CommandOptionsHelper {
+public:
+
+ /// \brief Wrapper class for allocated argv[] array.
+ ///
+ /// This class wraps allocated char** array and ensures that memory
+ /// allocated for this array is freed at the end o the scope.
+ class ArgvPtr {
+ public:
+ /// \brief Constructor.
+ ///
+ /// \param argv array of C-strings.
+ /// \param number of C-strings in the array.
+ ArgvPtr(char** argv, int argc) : argv_(argv), argc_(argc) { }
+
+ /// \brief Destructor.
+ ///
+ /// Dealocates wrapped array of C-strings.
+ ~ArgvPtr() {
+ if (argv_ != NULL) {
+ for(int i = 0; i < argc_; ++i) {
+ free(argv_[i]);
+ argv_[i] = NULL;
+ }
+ free(argv_);
+ }
+ }
+
+ /// \brief Return the array of C-strings.
+ ///
+ /// \return array of C-strings.
+ char** getArgv() const { return (argv_); }
+
+ /// \brief Return C-strings counter.
+ ///
+ /// \return C-strings counter.
+ int getArgc() const { return(argc_); }
+
+ public:
+ char** argv_; ///< array of C-strings being wrapped.
+ int argc_; ///< number of C-strings.
+ };
+
+ /// \brief Parse command line provided as string.
+ ///
+ /// Method transforms the string representing command line
+ /// to the array of C-strings consumed by the
+ /// \ref CommandOptions::parse function and performs
+ /// parsing.
+ ///
+ /// \param cmdline command line provided as single string.
+ static void process(const std::string& cmdline) {
+ CommandOptions& opt = CommandOptions::instance();
+ int argc = 0;
+ char** argv = tokenizeString(cmdline, argc);
+ ArgvPtr args(argv, argc);
+ opt.reset();
+ opt.parse(args.getArgc(), args.getArgv());
+ }
+
+private:
+
+ /// \brief Split string to the array of C-strings.
+ ///
+ /// \param text_to_split string to be splited.
+ /// \param [out] num number of substrings returned.
+ /// \return array of C-strings created from split.
+ static char** tokenizeString(const std::string& text_to_split, int& num) {
+ char** results = NULL;
+ // Tokenization with std streams
+ std::stringstream text_stream(text_to_split);
+ // Iterators to be used for tokenization
+ std::istream_iterator<std::string> text_iterator(text_stream);
+ std::istream_iterator<std::string> text_end;
+ // Tokenize string (space is a separator) using begin and end iteratos
+ std::vector<std::string> tokens(text_iterator, text_end);
+
+ if (tokens.size() > 0) {
+ // Allocate array of C-strings where we will store tokens
+ results = static_cast<char**>(malloc(tokens.size() * sizeof(char*)));
+ if (results == NULL) {
+ isc_throw(Unexpected, "unable to allocate array of c-strings");
+ }
+ // Store tokens in C-strings array
+ for (int i = 0; i < tokens.size(); ++i) {
+ char* cs = static_cast<char*>(malloc(tokens[i].length() + 1));
+ strcpy(cs, tokens[i].c_str());
+ results[i] = cs;
+ }
+ // Return number of tokens to calling function
+ num = tokens.size();
+ }
+ return results;
+ }
+};
+
+} // namespace isc::perfdhcp
+} // namespace isc
+
+#endif // __COMMAND_OPTIONS_HELPER_H
diff --git a/tests/tools/perfdhcp/tests/command_options_unittest.cc b/tests/tools/perfdhcp/tests/command_options_unittest.cc
index 8e1053d..0481d28 100644
--- a/tests/tools/perfdhcp/tests/command_options_unittest.cc
+++ b/tests/tools/perfdhcp/tests/command_options_unittest.cc
@@ -16,14 +16,17 @@
#include <stdint.h>
#include <string>
#include <gtest/gtest.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
-#include "../command_options.h"
+#include <dhcp/iface_mgr.h>
+#include <exceptions/exceptions.h>
-#include "exceptions/exceptions.h"
+#include "../command_options.h"
using namespace std;
using namespace isc;
using namespace isc::perfdhcp;
+using namespace boost::posix_time;
/// \brief Test Fixture Class
///
@@ -62,7 +65,7 @@ protected:
/// Check if initialized values are correct
void checkDefaults() {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp");
+ process("perfdhcp 192.168.0.1");
EXPECT_EQ(4, opt.getIpVersion());
EXPECT_EQ(CommandOptions::DORA_SARR, opt.getExchangeMode());
EXPECT_EQ(0, opt.getRate());
@@ -70,11 +73,45 @@ protected:
EXPECT_EQ(0, opt.getClientsNum());
// default mac
- uint8_t mac[6] = { 0x00, 0x0C, 0x01, 0x02, 0x03, 0x04 };
- std::vector<uint8_t> v1 = opt.getMacPrefix();
+ const uint8_t mac[6] = { 0x00, 0x0C, 0x01, 0x02, 0x03, 0x04 };
+ std::vector<uint8_t> v1 = opt.getMacTemplate();
ASSERT_EQ(6, v1.size());
EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac));
+ // Check if DUID is initialized. The DUID-LLT is expected
+ // to start with DUID_LLT value of 1 and hardware ethernet
+ // type equal to 1 (HWETHER_TYPE).
+ const uint8_t duid_llt_and_hw[4] = { 0x0, 0x1, 0x0, 0x1 };
+ // We assume DUID-LLT length 14. This includes 4 octets of
+ // DUID_LLT value, two octets of hardware type, 4 octets
+ // of time value and 6 octets of variable link layer (MAC)
+ // address.
+ const int duid_llt_size = 14;
+ // DUID is not given from the command line but it is supposed
+ // to be initialized by the CommandOptions private method
+ // generateDuidTemplate().
+ std::vector<uint8_t> v2 = opt.getDuidTemplate();
+ ASSERT_EQ(duid_llt_size, opt.getDuidTemplate().size());
+ EXPECT_TRUE(std::equal(v2.begin(), v2.begin() + 4,
+ duid_llt_and_hw));
+ // Check time field contents.
+ ptime now = microsec_clock::universal_time();
+ ptime duid_epoch(from_iso_string("20000101T000000"));
+ time_period period(duid_epoch, now);
+ uint32_t duration_sec = period.length().total_seconds();
+ // Read time from the template generated.
+ uint32_t duration_from_template = 0;
+ memcpy(&duration_from_template, &v2[4], 4);
+ duration_from_template = htonl(duration_from_template);
+ // In special cases, we may have overflow in time field
+ // so we give ourselves the margin of 10 seconds here.
+ // If time value has been set more then 10 seconds back
+ // it is safe to compare it with the time value generated
+ // from now.
+ if (duration_from_template > 10) {
+ EXPECT_GE(duration_sec, duration_from_template);
+ }
+
EXPECT_EQ(0, opt.getBase().size());
EXPECT_EQ(0, opt.getNumRequests().size());
EXPECT_EQ(0, opt.getPeriod());
@@ -104,7 +141,7 @@ protected:
EXPECT_GT(0, opt.getRequestedIpOffset());
EXPECT_EQ("", opt.getDiags());
EXPECT_EQ("", opt.getWrapped());
- EXPECT_EQ("", opt.getServerName());
+ EXPECT_EQ("192.168.0.1", opt.getServerName());
}
/// \brief Split string to array of C-strings
@@ -145,153 +182,210 @@ protected:
};
TEST_F(CommandOptionsTest, Defaults) {
- process("perfdhcp");
+ process("perfdhcp all");
checkDefaults();
}
TEST_F(CommandOptionsTest, UseFirst) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -1 -B -l ethx");
+ process("perfdhcp -1 -B -l ethx all");
EXPECT_TRUE(opt.isUseFirst());
}
TEST_F(CommandOptionsTest, IpVersion) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -6 -l ethx -c -i");
+ process("perfdhcp -6 -l ethx -c -i all");
EXPECT_EQ(6, opt.getIpVersion());
EXPECT_EQ("ethx", opt.getLocalName());
EXPECT_TRUE(opt.isRapidCommit());
EXPECT_FALSE(opt.isBroadcast());
- process("perfdhcp -4 -B -l ethx");
+ process("perfdhcp -4 -B -l ethx all");
EXPECT_EQ(4, opt.getIpVersion());
EXPECT_TRUE(opt.isBroadcast());
EXPECT_FALSE(opt.isRapidCommit());
// Negative test cases
// -4 and -6 must not coexist
- EXPECT_THROW(process("perfdhcp -4 -6 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -4 -6 -l ethx all"), isc::InvalidParameter);
// -6 and -B must not coexist
- EXPECT_THROW(process("perfdhcp -6 -B -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -6 -B -l ethx all"), isc::InvalidParameter);
// -c and -4 (default) must not coexist
- EXPECT_THROW(process("perfdhcp -c -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -c -l ethx all"), isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, Rate) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -4 -r 10 -l ethx");
+ process("perfdhcp -4 -r 10 -l ethx all");
EXPECT_EQ(10, opt.getRate());
// Negative test cases
// Rate must not be 0
- EXPECT_THROW(process("perfdhcp -4 -r 0 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -4 -r 0 -l ethx all"),
+ isc::InvalidParameter);
// -r must be specified to use -n, -p and -D
- EXPECT_THROW(process("perfdhcp -6 -t 5 -l ethx"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -4 -n 150 -l ethx"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -6 -p 120 -l ethx"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -4 -D 1400 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -6 -t 5 -l ethx all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -4 -n 150 -l ethx all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -6 -p 120 -l ethx all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -4 -D 1400 -l ethx all"),
+ isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, ReportDelay) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -r 100 -t 17 -l ethx");
+ process("perfdhcp -r 100 -t 17 -l ethx all");
EXPECT_EQ(17, opt.getReportDelay());
// Negative test cases
// -t must be positive integer
- EXPECT_THROW(process("perfdhcp -r 10 -t -8 -l ethx"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -r 10 -t 0 -l ethx"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -r 10 -t s -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -r 10 -t -8 -l ethx all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -r 10 -t 0 -l ethx all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -r 10 -t s -l ethx all"),
+ isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, ClientsNum) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -R 200 -l ethx");
+ process("perfdhcp -R 200 -l ethx all");
EXPECT_EQ(200, opt.getClientsNum());
- process("perfdhcp -R 0 -l ethx");
+ process("perfdhcp -R 0 -l ethx all");
EXPECT_EQ(0, opt.getClientsNum());
// Negative test cases
// Number of clients must be non-negative integer
- EXPECT_THROW(process("perfdhcp -R -5 -l ethx"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -R gs -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -R -5 -l ethx all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -R gs -l ethx all"),
+ isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, Base) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -6 -b MAC=10::20::30::40::50::60 -l ethx -b duiD=1AB7F5670901FF");
uint8_t mac[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60 };
- uint8_t duid[7] = { 0x1A, 0xB7, 0xF5, 0x67, 0x09, 0x01, 0xFF };
-
- // Test Mac
- std::vector<uint8_t> v1 = opt.getMacPrefix();
- ASSERT_EQ(6, v1.size());
+ uint8_t duid[14] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x10, 0x11, 0x1F, 0x14 };
+ // Test DUID and MAC together.
+ EXPECT_NO_THROW(process("perfdhcp -b DUID=0101010101010101010110111F14"
+ " -b MAC=10::20::30::40::50::60"
+ " -l 127.0.0.1 all"));
+ std::vector<uint8_t> v1 = opt.getMacTemplate();
+ std::vector<uint8_t> v2 = opt.getDuidTemplate();
+ v2 = opt.getDuidTemplate();
EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac));
- // "3x" is invalid value in MAC address
- EXPECT_THROW(process("perfdhcp -b mac=10::2::3x::4::5::6 -l ethx"), isc::InvalidParameter);
+ EXPECT_TRUE(std::equal(v2.begin(), v2.end(), duid));
+ // Test valid DUID.
+ EXPECT_NO_THROW(
+ process("perfdhcp -b duid=0101010101010101010110111F14 -l 127.0.0.1 all")
+ );
- // Test DUID
- std::vector<uint8_t> v2 = opt.getDuidPrefix();
ASSERT_EQ(sizeof(duid) / sizeof(uint8_t), v2.size());
EXPECT_TRUE(std::equal(v2.begin(), v2.end(), duid));
- // "t" is invalid digit in DUID
- EXPECT_THROW(process("perfdhcp -6 -l ethx -b duiD=1AB7Ft670901FF"), isc::InvalidParameter);
-
- // Some more negative test cases
+ // Test mix of upper/lower case letters.
+ EXPECT_NO_THROW(process("perfdhcp -b DuiD=0101010101010101010110111F14"
+ " -b Mac=10::20::30::40::50::60"
+ " -l 127.0.0.1 all"));
+ v1 = opt.getMacTemplate();
+ v2 = opt.getDuidTemplate();
+ EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac));
+ EXPECT_TRUE(std::equal(v2.begin(), v2.end(), duid));
+ // Use "ether" instead of "mac".
+ EXPECT_NO_THROW(process("perfdhcp -b ether=10::20::30::40::50::60"
+ " -l 127.0.0.1 all"));
+ v1 = opt.getMacTemplate();
+ EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac));
+ // Use "ETHER" in upper case.
+ EXPECT_NO_THROW(process("perfdhcp -b ETHER=10::20::30::40::50::60"
+ " -l 127.0.0.1 all"));
+ v1 = opt.getMacTemplate();
+ EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac));
+ // "t" is invalid character in DUID
+ EXPECT_THROW(process("perfdhcp -6 -l ethx -b "
+ "duid=010101010101010101t110111F14 all"),
+ isc::InvalidParameter);
+ // "3x" is invalid value in MAC address
+ EXPECT_THROW(process("perfdhcp -b mac=10::2::3x::4::5::6 -l ethx all"),
+ isc::InvalidParameter);
// Base is not specified
- EXPECT_THROW(process("perfdhcp -b -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -b -l ethx all"),
+ isc::InvalidParameter);
// Typo: should be mac= instead of mc=
- EXPECT_THROW(process("perfdhcp -l ethx -b mc=00:01:02:03::04:05"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -b mc=00:01:02:03::04:05 all"),
+ isc::InvalidParameter);
+ // Too short DUID (< 6).
+ EXPECT_THROW(process("perfdhcp -l ethx -b duid=00010203 all"),
+ isc::InvalidParameter);
+ // Odd number of digits.
+ EXPECT_THROW(process("perfdhcp -l ethx -b duid=000102030405060 all"),
+ isc::InvalidParameter);
+ // Too short MAC (!= 6).
+ EXPECT_THROW(process("perfdhcp -l ethx -b mac=00:01:02:04 all"),
+ isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, DropTime) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -l ethx -d 12");
+ process("perfdhcp -l ethx -d 12 all");
ASSERT_EQ(2, opt.getDropTime().size());
EXPECT_DOUBLE_EQ(12, opt.getDropTime()[0]);
EXPECT_DOUBLE_EQ(1, opt.getDropTime()[1]);
- process("perfdhcp -l ethx -d 2 -d 4.7");
+ process("perfdhcp -l ethx -d 2 -d 4.7 all");
ASSERT_EQ(2, opt.getDropTime().size());
EXPECT_DOUBLE_EQ(2, opt.getDropTime()[0]);
EXPECT_DOUBLE_EQ(4.7, opt.getDropTime()[1]);
// Negative test cases
// Drop time must not be negative
- EXPECT_THROW(process("perfdhcp -l ethx -d -2 -d 4.7"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -l ethx -d -9.1 -d 0"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -d -2 -d 4.7 all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -d -9.1 -d 0 all"),
+ isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, TimeOffset) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -l ethx -T file1.x -T file2.x -E 4");
+ process("perfdhcp -l ethx -T file1.x -T file2.x -E 4 all");
EXPECT_EQ(4, opt.getElapsedTimeOffset());
// Negative test cases
// Argument -E must be used with -T
- EXPECT_THROW(process("perfdhcp -l ethx -E 3 -i"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -E 3 -i all"),
+ isc::InvalidParameter);
// Value in -E not specified
- EXPECT_THROW(process("perfdhcp -l ethx -T file.x -E -i"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -T file.x -E -i all"),
+ isc::InvalidParameter);
// Value for -E must not be negative
- EXPECT_THROW(process("perfdhcp -l ethx -E -3 -T file.x"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -E -3 -T file.x all"),
+ isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, ExchangeMode) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -l ethx -i");
+ process("perfdhcp -l ethx -i all");
EXPECT_EQ(CommandOptions::DO_SA, opt.getExchangeMode());
// Negative test cases
// No template file specified
- EXPECT_THROW(process("perfdhcp -i -l ethx -X 3"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -i -l ethx -X 3 all"),
+ isc::InvalidParameter);
// Offsets can't be used in simple exchanges (-i)
- EXPECT_THROW(process("perfdhcp -i -l ethx -O 2 -T file.x"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -i -l ethx -E 3 -T file.x"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -i -l ethx -S 1 -T file.x"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -i -l ethx -I 2 -T file.x"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -i -l ethx -O 2 -T file.x all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -i -l ethx -E 3 -T file.x all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -i -l ethx -S 1 -T file.x all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -i -l ethx -I 2 -T file.x all"),
+ isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, Offsets) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -E5 -4 -I 2 -S3 -O 30 -X7 -l ethx -X3 -T file1.x -T file2.x");
+ process("perfdhcp -E5 -4 -I 2 -S3 -O 30 -X7 -l ethx "
+ "-X3 -T file1.x -T file2.x all");
EXPECT_EQ(2, opt.getRequestedIpOffset());
EXPECT_EQ(5, opt.getElapsedTimeOffset());
EXPECT_EQ(3, opt.getServerIdOffset());
@@ -304,151 +398,237 @@ TEST_F(CommandOptionsTest, Offsets) {
// Negative test cases
// IP offset/IA_NA offset must be positive
- EXPECT_THROW(process("perfdhcp -6 -I 0 -l ethx"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -6 -I -4 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -6 -I 0 -l ethx all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -6 -I -4 -l ethx all"),
+ isc::InvalidParameter);
// TODO - other negative cases
}
TEST_F(CommandOptionsTest, LocalPort) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -l ethx -L 2000");
+ process("perfdhcp -l ethx -L 2000 all");
EXPECT_EQ(2000, opt.getLocalPort());
// Negative test cases
// Local port must be between 0..65535
- EXPECT_THROW(process("perfdhcp -l ethx -L -2"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -l ethx -L"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -l ethx -L 65540"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -L -2 all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -L all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -L 65540 all"),
+ isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, Preload) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -1 -P 3 -l ethx");
+ process("perfdhcp -1 -P 3 -l ethx all");
EXPECT_EQ(3, opt.getPreload());
// Negative test cases
// Number of preload packages must not be negative integer
- EXPECT_THROW(process("perfdhcp -P -1 -l ethx"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -P -3 -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -P -1 -l ethx all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -P -3 -l ethx all"),
+ isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, Seed) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -6 -P 2 -s 23 -l ethx");
+ process("perfdhcp -6 -P 2 -s 23 -l ethx all");
EXPECT_EQ(23, opt.getSeed());
EXPECT_TRUE(opt.isSeeded());
- process("perfdhcp -6 -P 2 -s 0 -l ethx");
+ process("perfdhcp -6 -P 2 -s 0 -l ethx all");
EXPECT_EQ(0, opt.getSeed());
EXPECT_FALSE(opt.isSeeded());
// Negtaive test cases
// Seed must be non-negative integer
- EXPECT_THROW(process("perfdhcp -6 -P 2 -s -5 -l ethx"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -6 -P 2 -s -l ethx"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -6 -P 2 -s -5 -l ethx all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -6 -P 2 -s -l ethx all"),
+ isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, TemplateFiles) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -T file1.x -l ethx");
+ process("perfdhcp -T file1.x -l ethx all");
ASSERT_EQ(1, opt.getTemplateFiles().size());
EXPECT_EQ("file1.x", opt.getTemplateFiles()[0]);
- process("perfdhcp -T file1.x -s 12 -w start -T file2.x -4 -l ethx");
+ process("perfdhcp -T file1.x -s 12 -w start -T file2.x -4 -l ethx all");
ASSERT_EQ(2, opt.getTemplateFiles().size());
EXPECT_EQ("file1.x", opt.getTemplateFiles()[0]);
EXPECT_EQ("file2.x", opt.getTemplateFiles()[1]);
// Negative test cases
// No template file specified
- EXPECT_THROW(process("perfdhcp -s 12 -l ethx -T"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -s 12 -T -l ethx all"),
+ isc::InvalidParameter);
// Too many template files specified
- EXPECT_THROW(process("perfdhcp -s 12 -l ethx -T file.x -T file.x -T file.x"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -s 12 -l ethx -T file.x "
+ "-T file.x -T file.x all"),
+ isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, Wrapped) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -B -w start -i -l ethx");
+ process("perfdhcp -B -w start -i -l ethx all");
EXPECT_EQ("start", opt.getWrapped());
// Negative test cases
// Missing command after -w, expected start/stop
- EXPECT_THROW(process("perfdhcp -B -i -l ethx -w"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -B -i -l ethx -w all"),
+ isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, Diagnostics) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -l ethx -i -x asTe");
+ process("perfdhcp -l ethx -i -x asTe all");
EXPECT_EQ("asTe", opt.getDiags());
// Negative test cases
// No diagnostics string specified
- EXPECT_THROW(process("perfdhcp -l ethx -i -x"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -i -x all"),
+ isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, Aggressivity) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -a 10 -l 192.168.0.1");
+ process("perfdhcp -a 10 -l 192.168.0.1 all");
EXPECT_EQ(10, opt.getAggressivity());
// Negative test cases
// Aggressivity must be non negative integer
- EXPECT_THROW(process("perfdhcp -l ethx -a 0"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -l ethx -a"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -a -2 -l ethx -a 3"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -a 0 all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -l ethx -a all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -a -2 -l ethx -a 3 all"),
+ isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, MaxDrop) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -D 25 -l ethx -r 10");
+ process("perfdhcp -D 25 -l ethx -r 10 all");
EXPECT_EQ(25, opt.getMaxDrop()[0]);
- process("perfdhcp -D 25 -l ethx -D 15 -r 10");
+ process("perfdhcp -D 25 -l ethx -D 15 -r 10 all");
EXPECT_EQ(25, opt.getMaxDrop()[0]);
EXPECT_EQ(15, opt.getMaxDrop()[1]);
- process("perfdhcp -D 15% -l ethx -r 10");
+ process("perfdhcp -D 15% -l ethx -r 10 all");
EXPECT_EQ(15, opt.getMaxDropPercentage()[0]);
- process("perfdhcp -D 15% -D25% -l ethx -r 10");
+ process("perfdhcp -D 15% -D25% -l ethx -r 10 all");
EXPECT_EQ(15, opt.getMaxDropPercentage()[0]);
EXPECT_EQ(25, opt.getMaxDropPercentage()[1]);
- process("perfdhcp -D 1% -D 99% -l ethx -r 10");
+ process("perfdhcp -D 1% -D 99% -l ethx -r 10 all");
EXPECT_EQ(1, opt.getMaxDropPercentage()[0]);
EXPECT_EQ(99, opt.getMaxDropPercentage()[1]);
// Negative test cases
// Too many -D<value> options
- EXPECT_THROW(process("perfdhcp -D 0% -D 1 -l ethx -r20 -D 3"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -D 0% -D 1 -l ethx -r20 -D 3 all"),
+ isc::InvalidParameter);
// Too many -D<value%> options
- EXPECT_THROW(process("perfdhcp -D 99% -D 13% -l ethx -r20 -D 10%"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -D 99% -D 13% -l ethx -r20 -D 10% all"),
+ isc::InvalidParameter);
// Percentage is out of bounds
- EXPECT_THROW(process("perfdhcp -D101% -D 13% -l ethx -r20"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -D0% -D 13% -l ethx -r20"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -D101% -D 13% -l ethx -r20 all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -D0% -D 13% -l ethx -r20 all"),
+ isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, NumRequest) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -n 1000 -r 10 -l ethx");
+ process("perfdhcp -n 1000 -r 10 -l ethx all");
EXPECT_EQ(1000, opt.getNumRequests()[0]);
- process("perfdhcp -n 5 -r 10 -n 500 -l ethx");
+ process("perfdhcp -n 5 -r 10 -n 500 -l ethx all");
EXPECT_EQ(5, opt.getNumRequests()[0]);
EXPECT_EQ(500, opt.getNumRequests()[1]);
// Negative test cases
// Too many -n<value> parameters, expected maximum 2
- EXPECT_THROW(process("perfdhcp -n 1 -n 2 -l ethx -n3 -r 20"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -n 1 -n 2 -l ethx -n3 -r 20 all"),
+ isc::InvalidParameter);
// Num request must be positive integer
- EXPECT_THROW(process("perfdhcp -n 1 -n -22 -l ethx -r 10"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -n 0 -l ethx -r 10"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -n 1 -n -22 -l ethx -r 10 all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -n 0 -l ethx -r 10 all"),
+ isc::InvalidParameter);
}
TEST_F(CommandOptionsTest, Period) {
CommandOptions& opt = CommandOptions::instance();
- process("perfdhcp -p 120 -l ethx -r 100");
+ process("perfdhcp -p 120 -l ethx -r 100 all");
EXPECT_EQ(120, opt.getPeriod());
// Negative test cases
// Test period must be positive integer
- EXPECT_THROW(process("perfdhcp -p 0 -l ethx -r 50"), isc::InvalidParameter);
- EXPECT_THROW(process("perfdhcp -p -3 -l ethx -r 50"), isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -p 0 -l ethx -r 50 all"),
+ isc::InvalidParameter);
+ EXPECT_THROW(process("perfdhcp -p -3 -l ethx -r 50 all"),
+ isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, Interface) {
+ // In order to make this test portable we need to know
+ // at least one interface name on OS where test is run.
+ // Interface Manager has ability to detect interfaces.
+ // Altough we don't call initIsInterface explicitely
+ // here it is called by CommandOptions object interally
+ // so this function is covered by the test.
+ dhcp::IfaceMgr& iface_mgr = dhcp::IfaceMgr::instance();
+ const dhcp::IfaceMgr::IfaceCollection& ifaces = iface_mgr.getIfaces();
+ std::string iface_name;
+ CommandOptions& opt = CommandOptions::instance();
+ // The local loopback interface should be available.
+ // If no interface have been found for any reason we should
+ // not fail this test.
+ if (ifaces.size() > 0) {
+ // Get the name of the interface we detected.
+ iface_name = ifaces.begin()->getName();
+ // Use the name in the command parser.
+ ASSERT_NO_THROW(process("perfdhcp -4 -l " + iface_name + " abc"));
+ // We expect that command parser will detect that argument
+ // specified along with '-l' is the interface name.
+ EXPECT_TRUE(opt.isInterface());
+
+ // If neither interface nor server is specified then
+ // exception is expected to be thrown.
+ EXPECT_THROW(process("perfdhcp -4"), isc::InvalidParameter);
+ }
+}
+
+TEST_F(CommandOptionsTest, Server) {
+ CommandOptions& opt = CommandOptions::instance();
+ // There is at least server parameter needed. If server is not
+ // specified the local interface must be specified.
+ // The server value equal to 'all' means use broadcast.
+ ASSERT_NO_THROW(process("perfdhcp all"));
+ // Once command line is parsed we expect that server name is
+ // set to broadcast address because 'all' was specified.
+ EXPECT_TRUE(opt.isBroadcast());
+ // The broadcast address is 255.255.255.255.
+ EXPECT_EQ("255.255.255.255", opt.getServerName());
+
+ // When all is specified for DHCPv6 mode we expect
+ // FF02::1:2 as a server name which means All DHCP
+ // servers and relay agents in local network segment
+ ASSERT_NO_THROW(process("perfdhcp -6 all"));
+ EXPECT_EQ("FF02::1:2", opt.getServerName());
+
+ // When server='servers' in DHCPv6 mode we expect
+ // FF05::1:3 as server name which means All DHCP
+ // servers in local network.
+ ASSERT_NO_THROW(process("perfdhcp -6 servers"));
+ EXPECT_EQ("FF05::1:3", opt.getServerName());
+
+ // If server name is neither 'all' nor 'servers'
+ // the given argument value is expected to be
+ // returned.
+ ASSERT_NO_THROW(process("perfdhcp -6 abc"));
+ EXPECT_EQ("abc", opt.getServerName());
}
diff --git a/tests/tools/perfdhcp/tests/perf_pkt4_unittest.cc b/tests/tools/perfdhcp/tests/perf_pkt4_unittest.cc
index 3863faa..5523c64 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<PerfPkt4> pkt1(new PerfPkt4(&in_data[0],
+ in_data.size(),
+ transid_offset));
+ // Write numbers 4,5,6,7 to the packet's input buffer at position 10.
+ pkt1->writeAt(10, write_buf.begin() + 3, write_buf.begin() + 7);
+ // We have to pack data to output buffer here because Pkt4 provides no
+ // way to retrieve input buffer. If we pack data it will go to
+ // output buffer that has getter available.
+ ASSERT_TRUE(pkt1->rawPack());
+ const util::OutputBuffer& out_buf = pkt1->getBuffer();
+ ASSERT_EQ(in_data.size(), out_buf.getLength());
+ // Verify that 4,5,6,7 has been written to the packet's buffer.
+ const char* out_data = static_cast<const char*>(out_buf.getData());
+ EXPECT_TRUE(std::equal(write_buf.begin() + 3, write_buf.begin() + 7,
+ out_data + 10));
+ // Write 1 octet (0x51) at position 10.
+ pkt1->writeValueAt<uint8_t>(10, 0x51);
+ ASSERT_TRUE(pkt1->rawPack());
+ ASSERT_EQ(in_data.size(), pkt1->getBuffer().getLength());
+ EXPECT_EQ(0x51, pkt1->getBuffer()[10]);
+ // Write 2 octets (0x5251) at position 20.
+ pkt1->writeValueAt<uint16_t>(20, 0x5251);
+ ASSERT_TRUE(pkt1->rawPack());
+ ASSERT_EQ(in_data.size(), pkt1->getBuffer().getLength());
+ EXPECT_EQ(0x52, pkt1->getBuffer()[20]);
+ EXPECT_EQ(0x51, pkt1->getBuffer()[21]);
+ // Write 4 octets (0x54535251) at position 30.
+ pkt1->writeValueAt<uint32_t>(30, 0x54535251);
+ ASSERT_TRUE(pkt1->rawPack());
+ ASSERT_EQ(in_data.size(), pkt1->getBuffer().getLength());
+ EXPECT_EQ(0x54, pkt1->getBuffer()[30]);
+ EXPECT_EQ(0x53, pkt1->getBuffer()[31]);
+ EXPECT_EQ(0x52, pkt1->getBuffer()[32]);
+ EXPECT_EQ(0x51, pkt1->getBuffer()[33]);
+}
+
}
diff --git a/tests/tools/perfdhcp/tests/stats_mgr_unittest.cc b/tests/tools/perfdhcp/tests/stats_mgr_unittest.cc
index 2233847..d6b3aef 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 0000000..a4cde00
--- /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 <cstddef>
+#include <stdint.h>
+#include <string>
+#include <fstream>
+#include <gtest/gtest.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <exceptions/exceptions.h>
+#include <asiolink/io_address.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/iface_mgr.h>
+#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<uint8_t> MacAddress;
+ typedef MacAddress::iterator MacAddressIterator;
+
+ typedef std::vector<uint8_t> 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<uint8_t>& 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<std::vector<uint8_t> > 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<DuidIterator, DuidIterator> 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<std::vector<uint8_t> >::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<max-drops> is taken into account).
+ ///
+ /// \param iterations_num number of exchanges to simulate.
+ /// \param receive_num number of received OFFER packets.
+ /// \param iterations_performed actual number of iterations.
+ void testPkt4Exchange(int iterations_num,
+ int receive_num,
+ 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<Pkt4> 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<max-drops> is taken into account).
+ ///
+ /// \param iterations_num number of exchanges to simulate.
+ /// \param receive_num number of received OFFER packets.
+ /// \param iterations_performed actual number of iterations.
+ void testPkt6Exchange(int iterations_num,
+ int receive_num,
+ 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<Pkt6>
+ 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<std::vector<uint8_t> > 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<MacAddressIterator, MacAddressIterator> 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<std::vector<uint8_t> >::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<Pkt4>
+ createOfferPkt4(uint32_t transid) const {
+ boost::shared_ptr<Pkt4> offer(new Pkt4(DHCPOFFER, transid));
+ OptionPtr opt_msg_type = Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE,
+ OptionBuffer(DHCPOFFER));
+ 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<Pkt6>
+ createAdvertisePkt6(uint32_t transid) const {
+ OptionPtr opt_ia_na = Option::factory(Option::V6, D6O_IA_NA);
+ OptionPtr opt_serverid(new Option(Option::V6, D6O_SERVERID));
+ NakedTestControl tc;
+ uint8_t randomized = 0;
+ std::vector<uint8_t> duid(tc.generateDuid(randomized));
+ OptionPtr opt_clientid(Option::factory(Option::V6, D6O_CLIENTID, duid));
+ boost::shared_ptr<Pkt6> advertise(new Pkt6(DHCPV6_ADVERTISE, transid));
+ advertise->addOption(opt_ia_na);
+ advertise->addOption(opt_serverid);
+ advertise->addOption(opt_clientid);
+ advertise->updateTimestamp();
+ return (advertise);
+ }
+
+};
+
+TEST_F(TestControlTest, GenerateDuid) {
+ // 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> 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> 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<uint8_t> template1(256);
+ std::string file1("../templates/test1.hex");
+ std::vector<uint8_t> template2(233);
+ std::string file2("../templates/test2.hex");
+ for (int i = 0; i < template1.size(); ++i) {
+ template1[i] = static_cast<uint8_t>(random() % 256);
+ }
+ for (int i = 0; i < template2.size(); ++i) {
+ template2[i] = static_cast<uint8_t>(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.
+}
More information about the bind10-changes
mailing list