2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 14:05:33 +00:00

Merge branch 'trac1959'

This commit is contained in:
Marcin Siodelski
2012-09-12 16:14:51 +02:00
34 changed files with 5028 additions and 357 deletions

View File

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

View File

@@ -50,6 +50,15 @@ IfaceMgr::Iface::Iface(const std::string& name, int ifindex)
memset(mac_, 0, sizeof(mac_)); 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 std::string
IfaceMgr::Iface::getFullName() const { IfaceMgr::Iface::getFullName() const {
ostringstream tmp; ostringstream tmp;
@@ -138,15 +147,8 @@ IfaceMgr::IfaceMgr()
void IfaceMgr::closeSockets() { void IfaceMgr::closeSockets() {
for (IfaceCollection::iterator iface = ifaces_.begin(); for (IfaceCollection::iterator iface = ifaces_.begin();
iface != ifaces_.end(); ++iface) { iface != ifaces_.end(); ++iface) {
iface->closeSockets();
for (SocketCollection::iterator sock = iface->sockets_.begin();
sock != iface->sockets_.end(); ++sock) {
cout << "Closing socket " << sock->sockfd_ << endl;
close(sock->sockfd_);
}
iface->sockets_.clear();
} }
} }
IfaceMgr::~IfaceMgr() { IfaceMgr::~IfaceMgr() {
@@ -477,11 +479,34 @@ IfaceMgr::getLocalAddress(const IOAddress& remote_addr, const uint16_t port) {
asio::io_service io_service; asio::io_service io_service;
asio::ip::udp::socket sock(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; 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); sock.connect(remote_endpoint->getASIOEndpoint(), err_code);
if (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. // 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(); sock.local_endpoint();
asio::ip::address local_address(local_endpoint.address()); asio::ip::address local_address(local_endpoint.address());
// Close the socket.
sock.close();
// Return address of local endpoint. // Return address of local endpoint.
return IOAddress(local_address); return IOAddress(local_address);
} }
@@ -546,8 +574,9 @@ int IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port) {
memset(&addr6, 0, sizeof(addr6)); memset(&addr6, 0, sizeof(addr6));
addr6.sin6_family = AF_INET6; addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(port); addr6.sin6_port = htons(port);
if (addr.toText() != "::1") if (addr.toText() != "::1") {
addr6.sin6_scope_id = if_nametoindex(iface.getName().c_str()); addr6.sin6_scope_id = if_nametoindex(iface.getName().c_str());
}
memcpy(&addr6.sin6_addr, memcpy(&addr6.sin6_addr,
addr.getAddress().to_v6().to_bytes().data(), addr.getAddress().to_v6().to_bytes().data(),
@@ -724,7 +753,6 @@ IfaceMgr::send(const Pkt6Ptr& pkt) {
bool bool
IfaceMgr::send(const Pkt4Ptr& pkt) IfaceMgr::send(const Pkt4Ptr& pkt)
{ {
Iface* iface = getIface(pkt->getIface()); Iface* iface = getIface(pkt->getIface());
if (!iface) { if (!iface) {
isc_throw(BadValue, "Unable to send Pkt4. Invalid interface (" 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. /// provided set to indicated which sockets have something to read.
for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) { for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
for (SocketCollection::const_iterator s = iface->sockets_.begin(); const SocketCollection& socket_collection = iface->getSockets();
s != iface->sockets_.end(); ++s) { for (SocketCollection::const_iterator s = socket_collection.begin();
s != socket_collection.end(); ++s) {
// Only deal with IPv4 addresses. // Only deal with IPv4 addresses.
if (s->addr_.getFamily() == AF_INET) { 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 // Let's find out which interface/socket has the data
for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) { for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
for (SocketCollection::const_iterator s = iface->sockets_.begin(); const SocketCollection& socket_collection = iface->getSockets();
s != iface->sockets_.end(); ++s) { for (SocketCollection::const_iterator s = socket_collection.begin();
s != socket_collection.end(); ++s) {
if (FD_ISSET(s->sockfd_, &sockets)) { if (FD_ISSET(s->sockfd_, &sockets)) {
candidate = &(*s); candidate = &(*s);
break; break;
@@ -967,9 +997,9 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout) {
/// provided set to indicated which sockets have something to read. /// provided set to indicated which sockets have something to read.
IfaceCollection::const_iterator iface; IfaceCollection::const_iterator iface;
for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) { for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
const SocketCollection& socket_collection = iface->getSockets();
for (SocketCollection::const_iterator s = iface->sockets_.begin(); for (SocketCollection::const_iterator s = socket_collection.begin();
s != iface->sockets_.end(); ++s) { s != socket_collection.end(); ++s) {
// Only deal with IPv4 addresses. // Only deal with IPv4 addresses.
if (s->addr_.getFamily() == AF_INET6) { 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 // Let's find out which interface/socket has the data
for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) { for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
for (SocketCollection::const_iterator s = iface->sockets_.begin(); const SocketCollection& socket_collection = iface->getSockets();
s != iface->sockets_.end(); ++s) { for (SocketCollection::const_iterator s = socket_collection.begin();
s != socket_collection.end(); ++s) {
if (FD_ISSET(s->sockfd_, &sockets)) { if (FD_ISSET(s->sockfd_, &sockets)) {
candidate = &(*s); candidate = &(*s);
break; break;
@@ -1168,8 +1199,9 @@ uint16_t IfaceMgr::getSocket(const isc::dhcp::Pkt6& pkt) {
<< pkt.getIface()); << pkt.getIface());
} }
const SocketCollection& socket_collection = iface->getSockets();
SocketCollection::const_iterator s; 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) && if ((s->family_ == AF_INET6) &&
(!s->addr_.getAddress().to_v6().is_multicast())) { (!s->addr_.getAddress().to_v6().is_multicast())) {
return (s->sockfd_); return (s->sockfd_);
@@ -1190,8 +1222,9 @@ uint16_t IfaceMgr::getSocket(isc::dhcp::Pkt4 const& pkt) {
<< pkt.getIface()); << pkt.getIface());
} }
const SocketCollection& socket_collection = iface->getSockets();
SocketCollection::const_iterator s; 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) { if (s->family_ == AF_INET) {
return (s->sockfd_); return (s->sockfd_);
} }

View File

@@ -72,8 +72,10 @@ public:
}; };
/// type that holds a list of socket informations /// type that holds a list of socket informations
/// @todo: Add SocketCollectionConstIter type
typedef std::list<SocketInfo> SocketCollection; typedef std::list<SocketInfo> SocketCollection;
/// @brief represents a single network interface /// @brief represents a single network interface
/// ///
/// Iface structure represents network interface with all useful /// Iface structure represents network interface with all useful
@@ -89,6 +91,9 @@ public:
/// @param ifindex interface index (unique integer identifier) /// @param ifindex interface index (unique integer identifier)
Iface(const std::string& name, int ifindex); 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. /// @brief Returns full interface name as "ifname/ifindex" string.
/// ///
/// @return string with interface name /// @return string with interface name
@@ -192,11 +197,25 @@ public:
/// @return true if there was such socket, false otherwise /// @return true if there was such socket, false otherwise
bool delSocket(uint16_t sockfd); bool delSocket(uint16_t sockfd);
/// socket used to sending data /// @brief Returns collection of all sockets added to interface.
/// TODO: this should be protected ///
SocketCollection sockets_; /// 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: protected:
/// socket used to sending data
SocketCollection sockets_;
/// network interface name /// network interface name
std::string name_; std::string name_;

View File

@@ -15,6 +15,7 @@
#include <boost/shared_array.hpp> #include <boost/shared_array.hpp>
#include <boost/shared_ptr.hpp> #include <boost/shared_ptr.hpp>
#include <util/buffer.h> #include <util/buffer.h>
#include <exceptions/exceptions.h>
#include <dhcp/libdhcp++.h> #include <dhcp/libdhcp++.h>
#include "config.h" #include "config.h"
#include <dhcp/dhcp4.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_; 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, size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
isc::dhcp::Option::OptionCollection& options) { isc::dhcp::Option::OptionCollection& options) {
size_t offset = 0; size_t offset = 0;

View File

@@ -25,6 +25,26 @@ namespace dhcp {
class LibDHCP { class LibDHCP {
public: 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 collection of options.
/// ///
/// Builds raw (on-wire) data for provided collection of options. /// Builds raw (on-wire) data for provided collection of options.
@@ -84,10 +104,10 @@ public:
Option::Factory * factory); Option::Factory * factory);
protected: protected:
/// pointers to factories that produce DHCPv6 options /// 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 /// pointers to factories that produce DHCPv6 options
static std::map<unsigned short, Option::Factory*> v6factories_; static FactoryMap v6factories_;
}; };
} }

View File

@@ -29,6 +29,14 @@ using namespace isc::util;
namespace isc { namespace isc {
namespace dhcp { 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) Option::Option(Universe u, uint16_t type)
:universe_(u), type_(type) { :universe_(u), type_(type) {

View File

@@ -63,9 +63,45 @@ public:
/// @param type option type /// @param type option type
/// @param buf pointer to a buffer /// @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 /// @return a pointer to a created option object
typedef OptionPtr Factory(Option::Universe u, uint16_t type, const OptionBuffer& buf); 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 /// @brief ctor, used for options constructed, usually during transmission
/// ///
/// @param u option universe (DHCPv4 or DHCPv6) /// @param u option universe (DHCPv4 or DHCPv6)

View File

@@ -59,6 +59,7 @@ public:
~IfaceMgrTest() { ~IfaceMgrTest() {
} }
}; };
// We need some known interface to work reliably. Loopback interface // 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) { TEST_F(IfaceMgrTest, sockets6) {
// testing socket operation in a portable way is tricky // testing socket operation in a portable way is tricky
// without interface detection implemented // without interface detection implemented
@@ -317,6 +406,21 @@ TEST_F(IfaceMgrTest, socketsFromRemoteAddress) {
); );
EXPECT_GT(socket2, 0); EXPECT_GT(socket2, 0);
close(socket2); 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 // TODO: disabled due to other naming on various systems

View File

@@ -18,6 +18,8 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <util/buffer.h> #include <util/buffer.h>
#include <dhcp/dhcp4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/libdhcp++.h> #include <dhcp/libdhcp++.h>
#include "config.h" #include "config.h"
@@ -31,6 +33,19 @@ class LibDhcpTest : public ::testing::Test {
public: public:
LibDhcpTest() { 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[] = { static const uint8_t packed[] = {
@@ -41,6 +56,78 @@ static const uint8_t packed[] = {
1, 1, 0, 1, 114 // opt5 (5 bytes) 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) { TEST(LibDhcpTest, packOptions6) {
OptionBuffer buf(512); OptionBuffer buf(512);
isc::dhcp::Option::OptionCollection opts; // list of options isc::dhcp::Option::OptionCollection opts; // list of options

View File

@@ -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 -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log 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 AM_LDFLAGS += -static
endif endif
lib_LTLIBRARIES = libb10_perfdhcp++.la pkglibexec_PROGRAMS = perfdhcp2
libb10_perfdhcp___la_SOURCES = command_options.cc command_options.h perfdhcp2_SOURCES = main.cc
libb10_perfdhcp___la_SOURCES += localized_option.h perfdhcp2_SOURCES += command_options.cc command_options.h
libb10_perfdhcp___la_SOURCES += perf_pkt6.cc perf_pkt6.h perfdhcp2_SOURCES += localized_option.h
libb10_perfdhcp___la_SOURCES += perf_pkt4.cc perf_pkt4.h perfdhcp2_SOURCES += perf_pkt6.cc perf_pkt6.h
libb10_perfdhcp___la_SOURCES += pkt_transform.cc pkt_transform.h perfdhcp2_SOURCES += perf_pkt4.cc perf_pkt4.h
libb10_perfdhcp___la_SOURCES += stats_mgr.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) libb10_perfdhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
perfdhcp2_CXXFLAGS = $(AM_CXXFLAGS)
if USE_CLANGPP if USE_CLANGPP
# Disable unused parameter warning caused by some of the # Disable unused parameter warning caused by some of the
# Boost headers when compiling with clang. # Boost headers when compiling with clang.
libb10_perfdhcp___la_CXXFLAGS += -Wno-unused-parameter perfdhcp2_CXXFLAGS += -Wno-unused-parameter
endif endif
libb10_perfdhcp___la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la perfdhcp2_LDADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
libb10_perfdhcp___la_LIBADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la perfdhcp2_LDADD += $(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/asiolink/libb10-asiolink.la
pkglibexec_PROGRAMS = perfdhcp #pkglibexec_PROGRAMS = perfdhcp
perfdhcp_SOURCES = perfdhcp.c #perfdhcp_SOURCES = perfdhcp.c

View File

@@ -12,6 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE. // PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
@@ -20,9 +21,11 @@
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
#include <boost/lexical_cast.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" #include "command_options.h"
using namespace std; using namespace std;
@@ -54,9 +57,10 @@ CommandOptions::reset() {
rate_ = 0; rate_ = 0;
report_delay_ = 0; report_delay_ = 0;
clients_num_ = 0; clients_num_ = 0;
mac_prefix_.assign(mac, mac + 6); mac_template_.assign(mac, mac + 6);
base_.resize(0); duid_template_.clear();
num_request_.resize(0); base_.clear();
num_request_.clear();
period_ = 0; period_ = 0;
drop_time_set_ = 0; drop_time_set_ = 0;
drop_time_.assign(dt, dt + 2); drop_time_.assign(dt, dt + 2);
@@ -81,6 +85,8 @@ CommandOptions::reset() {
diags_.clear(); diags_.clear();
wrapped_.clear(); wrapped_.clear();
server_name_.clear(); server_name_.clear();
generateDuidTemplate();
commandline_.clear();
} }
void void
@@ -127,9 +133,16 @@ CommandOptions::initialize(int argc, char** argv) {
int offset_arg = 0; // Temporary variable holding offset arguments int offset_arg = 0; // Temporary variable holding offset arguments
std::string sarg; // Temporary variable for string args std::string sarg; // Temporary variable for string args
std::ostringstream stream;
stream << "perfdhcp";
// In this section we collect argument values from command line // In this section we collect argument values from command line
// they will be tuned and validated elsewhere // 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) { 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) { switch (opt) {
case 'v': case 'v':
version(); version();
@@ -219,6 +232,7 @@ CommandOptions::initialize(int argc, char** argv) {
case 'l': case 'l':
localname_ = std::string(optarg); localname_ = std::string(optarg);
initIsInterface();
break; break;
case 'L': 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 // If the IP version was not specified in the
// command line, assume IPv4. // command line, assume IPv4.
if (ipversion_ == 0) { 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 void
@@ -376,6 +412,17 @@ CommandOptions::initClientsNum() {
} }
} }
void
CommandOptions::initIsInterface() {
is_interface_ = false;
if (!localname_.empty()) {
dhcp::IfaceMgr& iface_mgr = dhcp::IfaceMgr::instance();
if (iface_mgr.getIface(localname_) != NULL) {
is_interface_ = true;
}
}
}
void void
CommandOptions::decodeBase(const std::string& base) { CommandOptions::decodeBase(const std::string& base) {
std::string b(base); std::string b(base);
@@ -402,7 +449,7 @@ CommandOptions::decodeMac(const std::string& base) {
// Decode mac address to vector of uint8_t // Decode mac address to vector of uint8_t
std::istringstream s1(base.substr(found + 1)); std::istringstream s1(base.substr(found + 1));
std::string token; std::string token;
mac_prefix_.clear(); mac_template_.clear();
// Get pieces of MAC address separated with : (or even ::) // Get pieces of MAC address separated with : (or even ::)
while (std::getline(s1, token, ':')) { while (std::getline(s1, token, ':')) {
unsigned int ui = 0; unsigned int ui = 0;
@@ -417,16 +464,17 @@ CommandOptions::decodeMac(const std::string& base) {
} }
// If conversion succeeded store byte value // 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 // MAC address must consist of 6 octets, otherwise it is invalid
check(mac_prefix_.size() != 6, errmsg); check(mac_template_.size() != 6, errmsg);
} }
void void
CommandOptions::decodeDuid(const std::string& base) { CommandOptions::decodeDuid(const std::string& base) {
// Strip argument from duid= // Strip argument from duid=
std::vector<uint8_t> duid_template;
size_t found = base.find('='); size_t found = base.find('=');
check(found == std::string::npos, "expected -b<base> format for duid is -b duid=<duid>"); check(found == std::string::npos, "expected -b<base> format for duid is -b duid=<duid>");
std::string b = base.substr(found + 1); std::string b = base.substr(found + 1);
@@ -446,8 +494,44 @@ CommandOptions::decodeDuid(const std::string& base) {
isc_throw(isc::InvalidParameter, isc_throw(isc::InvalidParameter,
"invalid characters in DUID provided, exepected hex digits"); "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 uint8_t
@@ -564,6 +648,98 @@ CommandOptions::nonEmptyString(const std::string& errmsg) const {
return sarg; return sarg;
} }
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 void
CommandOptions::usage() const { CommandOptions::usage() const {
fprintf(stdout, "%s", fprintf(stdout, "%s",
@@ -691,7 +867,7 @@ CommandOptions::usage() const {
void void
CommandOptions::version() const { CommandOptions::version() const {
fprintf(stdout, "version 0.01\n"); std::cout << "VERSION: " << VERSION << std::endl;
} }

View File

@@ -23,7 +23,7 @@
namespace isc { namespace isc {
namespace perfdhcp { namespace perfdhcp {
/// \brief Command Options /// \brief Command Options.
/// ///
/// This class is responsible for parsing the command-line and storing the /// This class is responsible for parsing the command-line and storing the
/// specified options. /// specified options.
@@ -49,64 +49,64 @@ public:
/// command line options. /// command line options.
void reset(); void reset();
/// \brief Parse command line /// \brief Parse command line.
/// ///
/// Parses the command line and stores the selected options /// Parses the command line and stores the selected options
/// in class data members. /// in class data members.
/// ///
/// \param argc Argument count passed to main(). /// \param argc Argument count passed to main().
/// \param argv Argument value array 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); 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_; } 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_; } 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_; } 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_; } 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_; } 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 /// \return MAC address template to simulate different clients.
std::vector<uint8_t> getMacPrefix() const { return mac_prefix_; } std::vector<uint8_t> getMacTemplate() const { return mac_template_; }
/// \brief Returns DUID prefix /// \brief Returns DUID template.
/// ///
/// \return DUID prefix to simulate different clients /// \return DUID template to simulate different clients.
std::vector<uint8_t> getDuidPrefix() const { return duid_prefix_; } 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_; } 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_; } 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_; } int getPeriod() const { return period_; }
/// \brief Returns drop time /// \brief Returns drop time
@@ -114,136 +114,139 @@ public:
/// The method returns maximum time elapsed from /// The method returns maximum time elapsed from
/// sending the packet before it is assumed dropped. /// 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_; } 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 /// Returns maximum number of packet drops before
/// aborting a test. /// aborting a test.
/// ///
/// \return maximum number of dropped requests /// \return maximum number of dropped requests.
std::vector<int> getMaxDrop() const { return max_drop_; } 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 /// Returns maximal percentage of packet drops
/// before aborting a test. /// before aborting a test.
/// ///
/// \return maximum percentage of lost requests /// \return maximum percentage of lost requests.
std::vector<double> getMaxDropPercentage() const { return max_pdrop_; } 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_; } 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 /// The method checks if interface name was used
/// rather than address. /// rather than address.
/// ///
/// \return true if interface name was used /// \return true if interface name was used.
bool isInterface() const { return is_interface_; } 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_; } int getPreload() const { return preload_; }
/// \brief Returns aggressivity value /// \brief Returns aggressivity value.
/// ///
/// \return aggressivity value /// \return aggressivity value.
int getAggressivity() const { return aggressivity_; } 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_; } 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_; } 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_; } 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_; } 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_; } 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_; } 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_; } 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_; } 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_; } 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_; } 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_; } 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_; } 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_; } 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_; } 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_; } 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; void usage() const;
/// \brief Print program version /// \brief Print program version.
/// ///
/// Prints perfdhcp version /// Prints perfdhcp version.
void version() const; void version() const;
private: private:
/// \brief Default Constructor /// \brief Default Constructor.
/// ///
/// Private constructor as this is a singleton class. /// Private constructor as this is a singleton class.
/// Use CommandOptions::instance() to get instance of it. /// Use CommandOptions::instance() to get instance of it.
@@ -251,57 +254,64 @@ private:
reset(); 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 argc Argument count passed to main().
/// \param argv Argument value array 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); 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; 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 /// 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 condition Condition to be checked.
/// \param errmsg Error message in exception /// \param errmsg Error message in exception.
/// \throws isc::InvalidParameter if condition argument true /// \throws isc::InvalidParameter if condition argument true.
inline void check(bool condition, const std::string& errmsg) const; 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 /// \param errmsg Error message if lexical cast fails.
/// \throw InvalidParameter if lexical cast fails /// \throw InvalidParameter if lexical cast fails.
int positiveInteger(const std::string& errmsg) const; 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 /// \param errmsg Error message if lexical cast fails.
/// \throw InvalidParameter if lexical cast fails /// \throw InvalidParameter if lexical cast fails.
int nonNegativeInteger(const std::string& errmsg) const; 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 /// \param errmsg Error message if string is empty.
/// \throw InvalidParameter if string is empty /// \throw InvalidParameter if string is empty.
std::string nonEmptyString(const std::string& errmsg) const; 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 /// Interprets the getopt() "opt" global variable as the number of clients
/// (a non-negative number). This value is specified by the "-R" switch. /// (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(); 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 /// Function decodes argument of -b switch, which
/// specifies a base value used to generate unique /// specifies a base value used to generate unique
@@ -311,39 +321,47 @@ private:
/// - -b mac=00:01:02:03:04:05 /// - -b mac=00:01:02:03:04:05
/// - -b duid=0F1234 (duid can be up to 128 hex digits) /// - -b duid=0F1234 (duid can be up to 128 hex digits)
// Function will decode 00:01:02:03:04:05 and/or // Function will decode 00:01:02:03:04:05 and/or
/// 0F1234 respectively and initialize mac_prefix_ /// 0F1234 respectively and initialize mac_template_
/// and/or duid_prefix_ members /// and/or duid_template_ members.
/// ///
/// \param base Base in string format /// \param base Base in string format.
/// \throws isc::InvalidParameter if base is invalid /// \throws isc::InvalidParameter if base is invalid.
void decodeBase(const std::string& base); 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 /// 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. /// 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 /// \param base Base string given as -b mac=00:01:02:03:04:05.
/// \throws isc::InvalidParameter if mac address is invalid /// \throws isc::InvalidParameter if mac address is invalid.
void decodeMac(const std::string& base); 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 /// Function decodes parameter given as -b duid=0F1234.
/// The function will decode 0F1234 and initialize duid_prefix_ /// The function will decode 0F1234 and initialize duid_template_
/// class member. /// class member.
/// Provided DUID is for example only. /// Provided DUID is for example only.
/// ///
/// \param base Base string given as -b duid=0F1234 /// \param base Base string given as -b duid=0F1234.
/// \throws isc::InvalidParameter if DUID is invalid /// \throws isc::InvalidParameter if DUID is invalid.
void decodeDuid(const std::string& base); void decodeDuid(const std::string& base);
/// \brief Converts two-digit hexadecimal string to a byte /// \brief Generates DUID-LLT (based on link layer address).
/// ///
/// \param hex_text Hexadecimal string e.g. AF /// Function generates DUID based on link layer address and
/// \throw isc::InvalidParameter if string does not represent hex byte /// initiates duid_template_ value with it.
/// \todo add support to generate DUIDs other than based on
/// 6-octets long MACs (e.g. DUID-UUID.
void generateDuidTemplate();
/// \brief Converts two-digit hexadecimal string to a byte.
///
/// \param hex_text Hexadecimal string e.g. AF.
/// \throw isc::InvalidParameter if string does not represent hex byte.
uint8_t convertHexString(const std::string& hex_text) const; uint8_t convertHexString(const std::string& hex_text) const;
uint8_t ipversion_; ///< IP protocol version to be used, expected values are: 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 int report_delay_; ///< Delay between generation of two consecutive
///< performance reports ///< performance reports
uint32_t clients_num_; ///< Number of simulated clients (aka randomization range). 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. ///< 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 ///< simulated clients
std::vector<std::string> base_; ///< Collection of base values specified with -b<value> std::vector<std::string> base_; ///< Collection of base values specified with -b<value>
///< options. Supported "bases" are mac=<mac> and duid=<duid> ///< options. Supported "bases" are mac=<mac> and duid=<duid>
@@ -404,6 +422,7 @@ private:
std::string wrapped_; ///< Wrapped command specified as -w<value>. Expected std::string wrapped_; ///< Wrapped command specified as -w<value>. Expected
///< values are start and stop. ///< values are start and stop.
std::string server_name_; ///< Server name specified as last argument of command line. 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 } // namespace perfdhcp

View File

@@ -16,6 +16,8 @@
#define __LOCALIZED_OPTION_H #define __LOCALIZED_OPTION_H
#include <dhcp/pkt6.h> #include <dhcp/pkt6.h>
#include <dhcp/option6_ia.h>
#include <util/buffer.h>
namespace isc { namespace isc {
namespace perfdhcp { namespace perfdhcp {
@@ -42,56 +44,30 @@ namespace perfdhcp {
/// ///
class LocalizedOption : public dhcp::Option { class LocalizedOption : public dhcp::Option {
public: 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) /// This constructor creates localized option using whole provided
/// \param type option type (0-255 for V4 and 0-65535 for V6) /// option buffer.
/// \param data content of the option ///
/// \param offset location of option in a packet (zero is default) /// \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, LocalizedOption(dhcp::Option::Universe u,
uint16_t type, uint16_t type,
const dhcp::OptionBuffer& data, const dhcp::OptionBuffer& data,
const size_t offset) : const size_t offset = 0) :
dhcp::Option(u, type, data), 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 /// This constructor creates localized option using part of the
/// the whole vector<uint8_t>, but rather subset of it. /// 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.
LocalizedOption(dhcp::Option::Universe u,
uint16_t type,
dhcp::OptionBufferConstIter first,
dhcp::OptionBufferConstIter last) :
dhcp::Option(u, type, first, last),
offset_(0) {
}
/// \brief Constructor, used to create option from buffer iterators
///
/// This contructor is similar to the previous one, but it does not take
/// the whole vector<uint8_t>, but rather subset of it.
/// ///
/// \param u specifies universe (V4 or V6) /// \param u specifies universe (V4 or V6)
/// \param type option type (0-255 for V4 and 0-65535 for V6) /// \param type option type (0-255 for V4 and 0-65535 for V6)
@@ -102,9 +78,52 @@ public:
LocalizedOption(dhcp::Option::Universe u, LocalizedOption(dhcp::Option::Universe u,
uint16_t type, uint16_t type,
dhcp::OptionBufferConstIter first, dhcp::OptionBufferConstIter first,
dhcp::OptionBufferConstIter last, const size_t offset) : dhcp::OptionBufferConstIter last,
const size_t offset = 0) :
dhcp::Option(u, type, first, last), dhcp::Option(u, type, first, last),
offset_(offset) { offset_(offset), option_valid_(true) {
}
/// \brief Copy constructor, creates LocalizedOption from Option6IA.
///
/// This copy constructor creates regular option from Option6IA.
/// The data from Option6IA data members are copied to
/// option buffer in appropriate sequence.
///
/// \param opt_ia option to be copied.
/// \param offset location of the option in a packet.
LocalizedOption(const boost::shared_ptr<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. /// \brief Returns offset of an option in a DHCP packet.
@@ -112,12 +131,20 @@ public:
/// \return option offset in a packet /// \return option offset in a packet
size_t getOffset() const { return offset_; }; 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: private:
size_t offset_; ///< Offset of DHCP option in a packet size_t offset_; ///< Offset of DHCP option in a packet
bool option_valid_; ///< Is option valid.
}; };
} // namespace perfdhcp } // namespace isc::perfdhcp
} // namespace isc } // namespace isc
#endif // __LOCALIZED_OPTION_H #endif // __LOCALIZED_OPTION_H

View File

@@ -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);
}

View File

@@ -16,7 +16,6 @@
#include <dhcp/dhcp6.h> #include <dhcp/dhcp6.h>
#include "perf_pkt4.h" #include "perf_pkt4.h"
#include "pkt_transform.h"
using namespace std; using namespace std;
using namespace isc; using namespace isc;
@@ -58,5 +57,14 @@ PerfPkt4::rawUnpack() {
return (res); 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 perfdhcp
} // namespace isc } // namespace isc

View File

@@ -20,6 +20,7 @@
#include <dhcp/pkt4.h> #include <dhcp/pkt4.h>
#include "localized_option.h" #include "localized_option.h"
#include "pkt_transform.h"
namespace isc { namespace isc {
namespace perfdhcp { namespace perfdhcp {
@@ -102,11 +103,36 @@ public:
/// \return false If unpack operation failed. /// \return false If unpack operation failed.
bool rawUnpack(); 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: private:
size_t transid_offset_; ///< transaction id offset size_t transid_offset_; ///< transaction id offset
}; };
typedef boost::shared_ptr<PerfPkt4> PerfPkt4Ptr;
} // namespace perfdhcp } // namespace perfdhcp
} // namespace isc } // namespace isc

View File

@@ -60,5 +60,13 @@ PerfPkt6::rawUnpack() {
return (res); 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 perfdhcp
} // namespace isc } // namespace isc

View File

@@ -20,6 +20,7 @@
#include <dhcp/pkt6.h> #include <dhcp/pkt6.h>
#include "localized_option.h" #include "localized_option.h"
#include "pkt_transform.h"
namespace isc { namespace isc {
namespace perfdhcp { namespace perfdhcp {
@@ -102,11 +103,36 @@ public:
/// \return false if unpack operation failed. /// \return false if unpack operation failed.
bool rawUnpack(); 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: private:
size_t transid_offset_; ///< transaction id offset size_t transid_offset_; ///< transaction id offset
}; };
typedef boost::shared_ptr<PerfPkt6> PerfPkt6Ptr;
} // namespace perfdhcp } // namespace perfdhcp
} // namespace isc } // namespace isc

View File

@@ -216,7 +216,13 @@ PktTransform::unpackOptions(const OptionBuffer& in_buffer,
in_buffer.begin() + offset + opt_len); 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 perfdhcp
} // namespace isc } // namespace isc

View File

@@ -92,6 +92,35 @@ public:
const size_t transid_offset, const size_t transid_offset,
uint32_t& transid); 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: private:
/// \brief Replaces contents of options in a buffer. /// \brief Replaces contents of options in a buffer.
/// ///
@@ -131,6 +160,7 @@ private:
/// \throw isc::Unexpected if options unpack failed. /// \throw isc::Unexpected if options unpack failed.
static void unpackOptions(const dhcp::OptionBuffer& in_buffer, static void unpackOptions(const dhcp::OptionBuffer& in_buffer,
const dhcp::Option::OptionCollection& options); const dhcp::Option::OptionCollection& options);
}; };
} // namespace perfdhcp } // namespace perfdhcp

View File

@@ -47,8 +47,8 @@ namespace perfdhcp {
/// stored on the list of sent packets. When packets are matched the /// stored on the list of sent packets. When packets are matched the
/// round trip time can be calculated. /// round trip time can be calculated.
/// ///
/// \tparam T class representing DHCPv4 or DHCPv6 packet. /// \param T class representing DHCPv4 or DHCPv6 packet.
template <class T> template <class T = dhcp::Pkt4>
class StatsMgr : public boost::noncopyable { class StatsMgr : public boost::noncopyable {
public: public:
@@ -138,7 +138,7 @@ public:
/// \param packet packet which transaction id is to be hashed. /// \param packet packet which transaction id is to be hashed.
/// \throw isc::BadValue if packet is null. /// \throw isc::BadValue if packet is null.
/// \return transaction id hash. /// \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) { if (!packet) {
isc_throw(BadValue, "Packet is null"); isc_throw(BadValue, "Packet is null");
} }
@@ -214,21 +214,33 @@ public:
/// } /// }
/// \endcode /// \endcode
typedef boost::multi_index_container< 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< 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<>, boost::multi_index::sequenced<>,
// The other index keeps products of transaction id.
boost::multi_index::hashed_non_unique< boost::multi_index::hashed_non_unique<
boost::multi_index::global_fun< // Specify hash function to get the product of
const boost::shared_ptr<const T>&, // transaction id. This product is obtained by calling
uint32_t, // hashTransid() function.
&ExchangeStats::hashTransid 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; > PktList;
/// Packet list iterator for sequencial access to elements. /// 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. /// Packet list index to search packets using transaction id hash.
typedef typename PktList::template nth_index<1>::type typedef typename PktList::template nth_index<1>::type
PktListTransidHashIndex; PktListTransidHashIndex;
@@ -243,20 +255,21 @@ public:
/// In this mode all packets are stored throughout the test execution. /// In this mode all packets are stored throughout the test execution.
ExchangeStats(const ExchangeType xchg_type, const bool archive_enabled) ExchangeStats(const ExchangeType xchg_type, const bool archive_enabled)
: xchg_type_(xchg_type), : xchg_type_(xchg_type),
min_delay_(std::numeric_limits<double>::max()),
max_delay_(0.),
sum_delay_(0.),
orphans_(0),
sum_delay_squared_(0.),
ordered_lookups_(0),
unordered_lookup_size_sum_(0),
unordered_lookups_(0),
sent_packets_num_(0),
rcvd_packets_num_(0),
sent_packets_(), sent_packets_(),
rcvd_packets_(), rcvd_packets_(),
archived_packets_(), archived_packets_(),
archive_enabled_(archive_enabled) { archive_enabled_(archive_enabled),
min_delay_(std::numeric_limits<double>::max()),
max_delay_(0.),
sum_delay_(0.),
sum_delay_squared_(0.),
orphans_(0),
unordered_lookup_size_sum_(0),
unordered_lookups_(0),
ordered_lookups_(0),
sent_packets_num_(0),
rcvd_packets_num_(0)
{
next_sent_ = sent_packets_.begin(); next_sent_ = sent_packets_.begin();
} }
@@ -266,7 +279,7 @@ public:
/// ///
/// \param packet packet object to be added. /// \param packet packet object to be added.
/// \throw isc::BadValue if packet is null. /// \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) { if (!packet) {
isc_throw(BadValue, "Packet is null"); isc_throw(BadValue, "Packet is null");
} }
@@ -280,7 +293,7 @@ public:
/// ///
/// \param packet packet object to be added. /// \param packet packet object to be added.
/// \throw isc::BadValue if packet is null. /// \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) { if (!packet) {
isc_throw(BadValue, "Packet is null"); isc_throw(BadValue, "Packet is null");
} }
@@ -296,8 +309,8 @@ public:
/// \param rcvd_packet received packet /// \param rcvd_packet received packet
/// \throw isc::BadValue if sent or received packet is null. /// \throw isc::BadValue if sent or received packet is null.
/// \throw isc::Unexpected if failed to calculate timestamps /// \throw isc::Unexpected if failed to calculate timestamps
void updateDelays(const boost::shared_ptr<const T>& sent_packet, void updateDelays(const boost::shared_ptr<T>& sent_packet,
const boost::shared_ptr<const T>& rcvd_packet) { const boost::shared_ptr<T>& rcvd_packet) {
if (!sent_packet) { if (!sent_packet) {
isc_throw(BadValue, "Sent packet is null"); isc_throw(BadValue, "Sent packet is null");
} }
@@ -355,7 +368,8 @@ public:
/// \throw isc::BadValue if received packet is null. /// \throw isc::BadValue if received packet is null.
/// \return packet having specified transaction or NULL if packet /// \return packet having specified transaction or NULL if packet
/// not found /// 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) { if (!rcvd_packet) {
isc_throw(BadValue, "Received packet is null"); isc_throw(BadValue, "Received packet is null");
} }
@@ -366,7 +380,7 @@ public:
// that the received packet we got has no corresponding // that the received packet we got has no corresponding
// sent packet so orphans counter has to be updated. // sent packet so orphans counter has to be updated.
++orphans_; ++orphans_;
return(boost::shared_ptr<const T>()); return(boost::shared_ptr<T>());
} else if (next_sent_ == sent_packets_.end()) { } else if (next_sent_ == sent_packets_.end()) {
// Even if there are still many unmatched packets on the // Even if there are still many unmatched packets on the
// list we might hit the end of it because of unordered // 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 // If we are here, it means that both ordered lookup and
// unordered lookup failed. Searched packet is not on the list. // unordered lookup failed. Searched packet is not on the list.
++orphans_; ++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 // Packet is matched so we count it. We don't count unmatched packets
// as they are counted as orphans with a separate counter. // as they are counted as orphans with a separate counter.
++rcvd_packets_num_; ++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 // If packet was found, we assume it will be never searched
// again. We want to delete this packet from the list to // again. We want to delete this packet from the list to
// improve performance of future searches. // improve performance of future searches.
@@ -548,6 +562,19 @@ public:
/// \return number of received packets. /// \return number of received packets.
uint64_t getRcvdPacketsNum() const { return(rcvd_packets_num_); } 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. /// \brief Print main statistics for packet exchange.
/// ///
/// Method prints main statistics for particular exchange. /// Method prints main statistics for particular exchange.
@@ -555,10 +582,9 @@ public:
/// number of dropped packets and number of orphans. /// number of dropped packets and number of orphans.
void printMainStats() const { void printMainStats() const {
using namespace std; using namespace std;
uint64_t drops = getRcvdPacketsNum() - getSentPacketsNum();
cout << "sent packets: " << getSentPacketsNum() << endl cout << "sent packets: " << getSentPacketsNum() << endl
<< "received packets: " << getRcvdPacketsNum() << endl << "received packets: " << getRcvdPacketsNum() << endl
<< "drops: " << drops << endl << "drops: " << getDroppedPacketsNum() << endl
<< "orphans: " << getOrphans() << endl; << "orphans: " << getOrphans() << endl;
} }
@@ -610,7 +636,7 @@ public:
for (PktListIterator it = rcvd_packets_.begin(); for (PktListIterator it = rcvd_packets_.begin();
it != rcvd_packets_.end(); it != rcvd_packets_.end();
++it) { ++it) {
boost::shared_ptr<const T> rcvd_packet = *it; boost::shared_ptr<T> rcvd_packet = *it;
PktListTransidHashIndex& idx = PktListTransidHashIndex& idx =
archived_packets_.template get<1>(); archived_packets_.template get<1>();
std::pair<PktListTransidHashIterator, std::pair<PktListTransidHashIterator,
@@ -621,7 +647,7 @@ public:
++it) { ++it) {
if ((*it_archived)->getTransid() == if ((*it_archived)->getTransid() ==
rcvd_packet->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. // Get sent and received packet times.
ptime sent_time = sent_packet->getTimestamp(); ptime sent_time = sent_packet->getTimestamp();
ptime rcvd_time = rcvd_packet->getTimestamp(); ptime rcvd_time = rcvd_packet->getTimestamp();
@@ -761,7 +787,8 @@ public:
StatsMgr(const bool archive_enabled = false) : StatsMgr(const bool archive_enabled = false) :
exchanges_(), exchanges_(),
custom_counters_(), custom_counters_(),
archive_enabled_(archive_enabled) { archive_enabled_(archive_enabled),
boot_time_(boost::posix_time::microsec_clock::universal_time()) {
} }
/// \brief Specify new exchange type. /// \brief Specify new exchange type.
@@ -819,7 +846,7 @@ public:
/// ///
/// \param counter_key key poitinh to the counter in the counters map. /// \param counter_key key poitinh to the counter in the counters map.
/// \return pointer to specified counter after incrementation. /// \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); CustomCounterPtr counter = getCounter(counter_key);
return(++(*counter)); return(++(*counter));
} }
@@ -835,7 +862,7 @@ public:
/// \throw isc::BadValue if invalid exchange type specified or /// \throw isc::BadValue if invalid exchange type specified or
/// packet is null. /// packet is null.
void passSentPacket(const ExchangeType xchg_type, 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); ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
xchg_stats->appendSent(packet); xchg_stats->appendSent(packet);
} }
@@ -853,10 +880,11 @@ public:
/// or packet is null. /// or packet is null.
/// \throw isc::Unexpected if corresponding packet was not /// \throw isc::Unexpected if corresponding packet was not
/// found on the list of sent packets. /// found on the list of sent packets.
void passRcvdPacket(const ExchangeType xchg_type, boost::shared_ptr<T>
const boost::shared_ptr<const T>& packet) { passRcvdPacket(const ExchangeType xchg_type,
const boost::shared_ptr<T>& packet) {
ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type); ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
boost::shared_ptr<const T> sent_packet boost::shared_ptr<T> sent_packet
= xchg_stats->matchPackets(packet); = xchg_stats->matchPackets(packet);
if (sent_packet) { if (sent_packet) {
@@ -865,6 +893,7 @@ public:
xchg_stats->appendRcvd(packet); xchg_stats->appendRcvd(packet);
} }
} }
return(sent_packet);
} }
/// \brief Return minumum delay between sent and received packet. /// \brief Return minumum delay between sent and received packet.
@@ -999,6 +1028,33 @@ public:
return(xchg_stats->getRcvdPacketsNum()); 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. /// \brief Return name of the exchange.
/// ///
/// Method returns name of the specified exchange type. /// 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. /// \brief Print timestamps of all packets.
/// ///
/// Method prints timestamps of all sent and received /// Method prints timestamps of all sent and received
@@ -1129,6 +1211,8 @@ private:
/// for extended period of time and many packets have to be /// for extended period of time and many packets have to be
/// archived. /// archived.
bool archive_enabled_; bool archive_enabled_;
boost::posix_time::ptime boot_time_; ///< Time when test is started.
}; };
} // namespace perfdhcp } // namespace perfdhcp

View File

@@ -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

View File

@@ -0,0 +1 @@
01010601008b45d200000000000000000000000000000000ac100102000c0102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501013707011c02030f060cff

View File

@@ -0,0 +1 @@
01010601007b23f800000000000000000000000000000000ac100102000c0102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633204ac1001813501033604ac1001013707011c02030f060cff

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -22,10 +22,13 @@ run_unittests_SOURCES += perf_pkt6_unittest.cc
run_unittests_SOURCES += perf_pkt4_unittest.cc run_unittests_SOURCES += perf_pkt4_unittest.cc
run_unittests_SOURCES += localized_option_unittest.cc run_unittests_SOURCES += localized_option_unittest.cc
run_unittests_SOURCES += stats_mgr_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/command_options.cc
run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/pkt_transform.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_pkt6.cc
run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt4.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_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)

View File

@@ -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

View File

@@ -16,14 +16,17 @@
#include <stdint.h> #include <stdint.h>
#include <string> #include <string>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <dhcp/iface_mgr.h>
#include <exceptions/exceptions.h>
#include "../command_options.h" #include "../command_options.h"
#include "exceptions/exceptions.h"
using namespace std; using namespace std;
using namespace isc; using namespace isc;
using namespace isc::perfdhcp; using namespace isc::perfdhcp;
using namespace boost::posix_time;
/// \brief Test Fixture Class /// \brief Test Fixture Class
/// ///
@@ -62,7 +65,7 @@ protected:
/// Check if initialized values are correct /// Check if initialized values are correct
void checkDefaults() { void checkDefaults() {
CommandOptions& opt = CommandOptions::instance(); CommandOptions& opt = CommandOptions::instance();
process("perfdhcp"); process("perfdhcp 192.168.0.1");
EXPECT_EQ(4, opt.getIpVersion()); EXPECT_EQ(4, opt.getIpVersion());
EXPECT_EQ(CommandOptions::DORA_SARR, opt.getExchangeMode()); EXPECT_EQ(CommandOptions::DORA_SARR, opt.getExchangeMode());
EXPECT_EQ(0, opt.getRate()); EXPECT_EQ(0, opt.getRate());
@@ -70,11 +73,45 @@ protected:
EXPECT_EQ(0, opt.getClientsNum()); EXPECT_EQ(0, opt.getClientsNum());
// default mac // default mac
uint8_t mac[6] = { 0x00, 0x0C, 0x01, 0x02, 0x03, 0x04 }; const uint8_t mac[6] = { 0x00, 0x0C, 0x01, 0x02, 0x03, 0x04 };
std::vector<uint8_t> v1 = opt.getMacPrefix(); std::vector<uint8_t> v1 = opt.getMacTemplate();
ASSERT_EQ(6, v1.size()); ASSERT_EQ(6, v1.size());
EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac)); 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.getBase().size());
EXPECT_EQ(0, opt.getNumRequests().size()); EXPECT_EQ(0, opt.getNumRequests().size());
EXPECT_EQ(0, opt.getPeriod()); EXPECT_EQ(0, opt.getPeriod());
@@ -104,7 +141,7 @@ protected:
EXPECT_GT(0, opt.getRequestedIpOffset()); EXPECT_GT(0, opt.getRequestedIpOffset());
EXPECT_EQ("", opt.getDiags()); EXPECT_EQ("", opt.getDiags());
EXPECT_EQ("", opt.getWrapped()); EXPECT_EQ("", opt.getWrapped());
EXPECT_EQ("", opt.getServerName()); EXPECT_EQ("192.168.0.1", opt.getServerName());
} }
/// \brief Split string to array of C-strings /// \brief Split string to array of C-strings
@@ -145,153 +182,210 @@ protected:
}; };
TEST_F(CommandOptionsTest, Defaults) { TEST_F(CommandOptionsTest, Defaults) {
process("perfdhcp"); process("perfdhcp all");
checkDefaults(); checkDefaults();
} }
TEST_F(CommandOptionsTest, UseFirst) { TEST_F(CommandOptionsTest, UseFirst) {
CommandOptions& opt = CommandOptions::instance(); CommandOptions& opt = CommandOptions::instance();
process("perfdhcp -1 -B -l ethx"); process("perfdhcp -1 -B -l ethx all");
EXPECT_TRUE(opt.isUseFirst()); EXPECT_TRUE(opt.isUseFirst());
} }
TEST_F(CommandOptionsTest, IpVersion) { TEST_F(CommandOptionsTest, IpVersion) {
CommandOptions& opt = CommandOptions::instance(); 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(6, opt.getIpVersion());
EXPECT_EQ("ethx", opt.getLocalName()); EXPECT_EQ("ethx", opt.getLocalName());
EXPECT_TRUE(opt.isRapidCommit()); EXPECT_TRUE(opt.isRapidCommit());
EXPECT_FALSE(opt.isBroadcast()); EXPECT_FALSE(opt.isBroadcast());
process("perfdhcp -4 -B -l ethx"); process("perfdhcp -4 -B -l ethx all");
EXPECT_EQ(4, opt.getIpVersion()); EXPECT_EQ(4, opt.getIpVersion());
EXPECT_TRUE(opt.isBroadcast()); EXPECT_TRUE(opt.isBroadcast());
EXPECT_FALSE(opt.isRapidCommit()); EXPECT_FALSE(opt.isRapidCommit());
// Negative test cases // Negative test cases
// -4 and -6 must not coexist // -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 // -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 // -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) { TEST_F(CommandOptionsTest, Rate) {
CommandOptions& opt = CommandOptions::instance(); CommandOptions& opt = CommandOptions::instance();
process("perfdhcp -4 -r 10 -l ethx"); process("perfdhcp -4 -r 10 -l ethx all");
EXPECT_EQ(10, opt.getRate()); EXPECT_EQ(10, opt.getRate());
// Negative test cases // Negative test cases
// Rate must not be 0 // 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 // -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 -6 -t 5 -l ethx all"),
EXPECT_THROW(process("perfdhcp -4 -n 150 -l ethx"), isc::InvalidParameter); isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -6 -p 120 -l ethx"), isc::InvalidParameter); EXPECT_THROW(process("perfdhcp -4 -n 150 -l ethx all"),
EXPECT_THROW(process("perfdhcp -4 -D 1400 -l ethx"), isc::InvalidParameter); 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) { TEST_F(CommandOptionsTest, ReportDelay) {
CommandOptions& opt = CommandOptions::instance(); 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()); EXPECT_EQ(17, opt.getReportDelay());
// Negative test cases // Negative test cases
// -t must be positive integer // -t must be positive integer
EXPECT_THROW(process("perfdhcp -r 10 -t -8 -l ethx"), isc::InvalidParameter); EXPECT_THROW(process("perfdhcp -r 10 -t -8 -l ethx all"),
EXPECT_THROW(process("perfdhcp -r 10 -t 0 -l ethx"), isc::InvalidParameter); isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -r 10 -t s -l ethx"), 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) { TEST_F(CommandOptionsTest, ClientsNum) {
CommandOptions& opt = CommandOptions::instance(); CommandOptions& opt = CommandOptions::instance();
process("perfdhcp -R 200 -l ethx"); process("perfdhcp -R 200 -l ethx all");
EXPECT_EQ(200, opt.getClientsNum()); EXPECT_EQ(200, opt.getClientsNum());
process("perfdhcp -R 0 -l ethx"); process("perfdhcp -R 0 -l ethx all");
EXPECT_EQ(0, opt.getClientsNum()); EXPECT_EQ(0, opt.getClientsNum());
// Negative test cases // Negative test cases
// Number of clients must be non-negative integer // Number of clients must be non-negative integer
EXPECT_THROW(process("perfdhcp -R -5 -l ethx"), isc::InvalidParameter); EXPECT_THROW(process("perfdhcp -R -5 -l ethx all"),
EXPECT_THROW(process("perfdhcp -R gs -l ethx"), isc::InvalidParameter); isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -R gs -l ethx all"),
isc::InvalidParameter);
} }
TEST_F(CommandOptionsTest, Base) { TEST_F(CommandOptionsTest, Base) {
CommandOptions& opt = CommandOptions::instance(); 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 mac[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60 };
uint8_t duid[7] = { 0x1A, 0xB7, 0xF5, 0x67, 0x09, 0x01, 0xFF }; uint8_t duid[14] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x10, 0x11, 0x1F, 0x14 };
// Test Mac // Test DUID and MAC together.
std::vector<uint8_t> v1 = opt.getMacPrefix(); EXPECT_NO_THROW(process("perfdhcp -b DUID=0101010101010101010110111F14"
ASSERT_EQ(6, v1.size()); " -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)); EXPECT_TRUE(std::equal(v1.begin(), v1.end(), mac));
// "3x" is invalid value in MAC address EXPECT_TRUE(std::equal(v2.begin(), v2.end(), duid));
EXPECT_THROW(process("perfdhcp -b mac=10::2::3x::4::5::6 -l ethx"), isc::InvalidParameter); // 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()); ASSERT_EQ(sizeof(duid) / sizeof(uint8_t), v2.size());
EXPECT_TRUE(std::equal(v2.begin(), v2.end(), duid)); EXPECT_TRUE(std::equal(v2.begin(), v2.end(), duid));
// "t" is invalid digit in DUID // Test mix of upper/lower case letters.
EXPECT_THROW(process("perfdhcp -6 -l ethx -b duiD=1AB7Ft670901FF"), isc::InvalidParameter); EXPECT_NO_THROW(process("perfdhcp -b DuiD=0101010101010101010110111F14"
" -b Mac=10::20::30::40::50::60"
// Some more negative test cases " -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 // 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= // 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) { TEST_F(CommandOptionsTest, DropTime) {
CommandOptions& opt = CommandOptions::instance(); CommandOptions& opt = CommandOptions::instance();
process("perfdhcp -l ethx -d 12"); process("perfdhcp -l ethx -d 12 all");
ASSERT_EQ(2, opt.getDropTime().size()); ASSERT_EQ(2, opt.getDropTime().size());
EXPECT_DOUBLE_EQ(12, opt.getDropTime()[0]); EXPECT_DOUBLE_EQ(12, opt.getDropTime()[0]);
EXPECT_DOUBLE_EQ(1, opt.getDropTime()[1]); 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()); ASSERT_EQ(2, opt.getDropTime().size());
EXPECT_DOUBLE_EQ(2, opt.getDropTime()[0]); EXPECT_DOUBLE_EQ(2, opt.getDropTime()[0]);
EXPECT_DOUBLE_EQ(4.7, opt.getDropTime()[1]); EXPECT_DOUBLE_EQ(4.7, opt.getDropTime()[1]);
// Negative test cases // Negative test cases
// Drop time must not be negative // 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 -2 -d 4.7 all"),
EXPECT_THROW(process("perfdhcp -l ethx -d -9.1 -d 0"), isc::InvalidParameter); isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -l ethx -d -9.1 -d 0 all"),
isc::InvalidParameter);
} }
TEST_F(CommandOptionsTest, TimeOffset) { TEST_F(CommandOptionsTest, TimeOffset) {
CommandOptions& opt = CommandOptions::instance(); 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()); EXPECT_EQ(4, opt.getElapsedTimeOffset());
// Negative test cases // Negative test cases
// Argument -E must be used with -T // 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 // 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 // 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) { TEST_F(CommandOptionsTest, ExchangeMode) {
CommandOptions& opt = CommandOptions::instance(); CommandOptions& opt = CommandOptions::instance();
process("perfdhcp -l ethx -i"); process("perfdhcp -l ethx -i all");
EXPECT_EQ(CommandOptions::DO_SA, opt.getExchangeMode()); EXPECT_EQ(CommandOptions::DO_SA, opt.getExchangeMode());
// Negative test cases // Negative test cases
// No template file specified // 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) // 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 -O 2 -T file.x all"),
EXPECT_THROW(process("perfdhcp -i -l ethx -E 3 -T file.x"), isc::InvalidParameter); isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -i -l ethx -S 1 -T file.x"), isc::InvalidParameter); EXPECT_THROW(process("perfdhcp -i -l ethx -E 3 -T file.x all"),
EXPECT_THROW(process("perfdhcp -i -l ethx -I 2 -T file.x"), isc::InvalidParameter); 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) { TEST_F(CommandOptionsTest, Offsets) {
CommandOptions& opt = CommandOptions::instance(); 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(2, opt.getRequestedIpOffset());
EXPECT_EQ(5, opt.getElapsedTimeOffset()); EXPECT_EQ(5, opt.getElapsedTimeOffset());
EXPECT_EQ(3, opt.getServerIdOffset()); EXPECT_EQ(3, opt.getServerIdOffset());
@@ -304,151 +398,237 @@ TEST_F(CommandOptionsTest, Offsets) {
// Negative test cases // Negative test cases
// IP offset/IA_NA offset must be positive // 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 0 -l ethx all"),
EXPECT_THROW(process("perfdhcp -6 -I -4 -l ethx"), isc::InvalidParameter); isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -6 -I -4 -l ethx all"),
isc::InvalidParameter);
// TODO - other negative cases // TODO - other negative cases
} }
TEST_F(CommandOptionsTest, LocalPort) { TEST_F(CommandOptionsTest, LocalPort) {
CommandOptions& opt = CommandOptions::instance(); CommandOptions& opt = CommandOptions::instance();
process("perfdhcp -l ethx -L 2000"); process("perfdhcp -l ethx -L 2000 all");
EXPECT_EQ(2000, opt.getLocalPort()); EXPECT_EQ(2000, opt.getLocalPort());
// Negative test cases // Negative test cases
// Local port must be between 0..65535 // Local port must be between 0..65535
EXPECT_THROW(process("perfdhcp -l ethx -L -2"), isc::InvalidParameter); EXPECT_THROW(process("perfdhcp -l ethx -L -2 all"),
EXPECT_THROW(process("perfdhcp -l ethx -L"), isc::InvalidParameter); isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -l ethx -L 65540"), 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) { TEST_F(CommandOptionsTest, Preload) {
CommandOptions& opt = CommandOptions::instance(); CommandOptions& opt = CommandOptions::instance();
process("perfdhcp -1 -P 3 -l ethx"); process("perfdhcp -1 -P 3 -l ethx all");
EXPECT_EQ(3, opt.getPreload()); EXPECT_EQ(3, opt.getPreload());
// Negative test cases // Negative test cases
// Number of preload packages must not be negative integer // Number of preload packages must not be negative integer
EXPECT_THROW(process("perfdhcp -P -1 -l ethx"), isc::InvalidParameter); EXPECT_THROW(process("perfdhcp -P -1 -l ethx all"),
EXPECT_THROW(process("perfdhcp -P -3 -l ethx"), isc::InvalidParameter); isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -P -3 -l ethx all"),
isc::InvalidParameter);
} }
TEST_F(CommandOptionsTest, Seed) { TEST_F(CommandOptionsTest, Seed) {
CommandOptions& opt = CommandOptions::instance(); 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_EQ(23, opt.getSeed());
EXPECT_TRUE(opt.isSeeded()); 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_EQ(0, opt.getSeed());
EXPECT_FALSE(opt.isSeeded()); EXPECT_FALSE(opt.isSeeded());
// Negtaive test cases // Negtaive test cases
// Seed must be non-negative integer // 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 -5 -l ethx all"),
EXPECT_THROW(process("perfdhcp -6 -P 2 -s -l ethx"), isc::InvalidParameter); isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -6 -P 2 -s -l ethx all"),
isc::InvalidParameter);
} }
TEST_F(CommandOptionsTest, TemplateFiles) { TEST_F(CommandOptionsTest, TemplateFiles) {
CommandOptions& opt = CommandOptions::instance(); 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()); ASSERT_EQ(1, opt.getTemplateFiles().size());
EXPECT_EQ("file1.x", opt.getTemplateFiles()[0]); 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()); ASSERT_EQ(2, opt.getTemplateFiles().size());
EXPECT_EQ("file1.x", opt.getTemplateFiles()[0]); EXPECT_EQ("file1.x", opt.getTemplateFiles()[0]);
EXPECT_EQ("file2.x", opt.getTemplateFiles()[1]); EXPECT_EQ("file2.x", opt.getTemplateFiles()[1]);
// Negative test cases // Negative test cases
// No template file specified // 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 // 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) { TEST_F(CommandOptionsTest, Wrapped) {
CommandOptions& opt = CommandOptions::instance(); 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()); EXPECT_EQ("start", opt.getWrapped());
// Negative test cases // Negative test cases
// Missing command after -w, expected start/stop // 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) { TEST_F(CommandOptionsTest, Diagnostics) {
CommandOptions& opt = CommandOptions::instance(); CommandOptions& opt = CommandOptions::instance();
process("perfdhcp -l ethx -i -x asTe"); process("perfdhcp -l ethx -i -x asTe all");
EXPECT_EQ("asTe", opt.getDiags()); EXPECT_EQ("asTe", opt.getDiags());
// Negative test cases // Negative test cases
// No diagnostics string specified // 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) { TEST_F(CommandOptionsTest, Aggressivity) {
CommandOptions& opt = CommandOptions::instance(); 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()); EXPECT_EQ(10, opt.getAggressivity());
// Negative test cases // Negative test cases
// Aggressivity must be non negative integer // Aggressivity must be non negative integer
EXPECT_THROW(process("perfdhcp -l ethx -a 0"), isc::InvalidParameter); EXPECT_THROW(process("perfdhcp -l ethx -a 0 all"),
EXPECT_THROW(process("perfdhcp -l ethx -a"), isc::InvalidParameter); isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -a -2 -l ethx -a 3"), 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) { TEST_F(CommandOptionsTest, MaxDrop) {
CommandOptions& opt = CommandOptions::instance(); 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]); 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(25, opt.getMaxDrop()[0]);
EXPECT_EQ(15, opt.getMaxDrop()[1]); 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]); 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(15, opt.getMaxDropPercentage()[0]);
EXPECT_EQ(25, opt.getMaxDropPercentage()[1]); 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(1, opt.getMaxDropPercentage()[0]);
EXPECT_EQ(99, opt.getMaxDropPercentage()[1]); EXPECT_EQ(99, opt.getMaxDropPercentage()[1]);
// Negative test cases // Negative test cases
// Too many -D<value> options // 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 // 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 // Percentage is out of bounds
EXPECT_THROW(process("perfdhcp -D101% -D 13% -l ethx -r20"), isc::InvalidParameter); EXPECT_THROW(process("perfdhcp -D101% -D 13% -l ethx -r20 all"),
EXPECT_THROW(process("perfdhcp -D0% -D 13% -l ethx -r20"), isc::InvalidParameter); isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -D0% -D 13% -l ethx -r20 all"),
isc::InvalidParameter);
} }
TEST_F(CommandOptionsTest, NumRequest) { TEST_F(CommandOptionsTest, NumRequest) {
CommandOptions& opt = CommandOptions::instance(); 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]); 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(5, opt.getNumRequests()[0]);
EXPECT_EQ(500, opt.getNumRequests()[1]); EXPECT_EQ(500, opt.getNumRequests()[1]);
// Negative test cases // Negative test cases
// Too many -n<value> parameters, expected maximum 2 // 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 // 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 1 -n -22 -l ethx -r 10 all"),
EXPECT_THROW(process("perfdhcp -n 0 -l ethx -r 10"), isc::InvalidParameter); isc::InvalidParameter);
EXPECT_THROW(process("perfdhcp -n 0 -l ethx -r 10 all"),
isc::InvalidParameter);
} }
TEST_F(CommandOptionsTest, Period) { TEST_F(CommandOptionsTest, Period) {
CommandOptions& opt = CommandOptions::instance(); 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()); EXPECT_EQ(120, opt.getPeriod());
// Negative test cases // Negative test cases
// Test period must be positive integer // Test period must be positive integer
EXPECT_THROW(process("perfdhcp -p 0 -l ethx -r 50"), isc::InvalidParameter); EXPECT_THROW(process("perfdhcp -p 0 -l ethx -r 50 all"),
EXPECT_THROW(process("perfdhcp -p -3 -l ethx -r 50"), isc::InvalidParameter); 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());
} }

View File

@@ -381,4 +381,50 @@ TEST_F(PerfPkt4Test, UnpackTransactionId) {
EXPECT_FALSE(pkt2->rawUnpack()); 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]);
}
} }

View File

@@ -387,13 +387,13 @@ TEST_F(StatsMgrTest, CustomCounters) {
// Increment one of the counters 10 times. // Increment one of the counters 10 times.
const uint64_t tooshort_num = 10; const uint64_t tooshort_num = 10;
for (uint64_t i = 0; i < tooshort_num; ++i) { 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. // Increment another counter by 5 times.
const uint64_t toolate_num = 5; const uint64_t toolate_num = 5;
for (uint64_t i = 0; i < toolate_num; ++i) { 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. // Check counter's current value and name.

File diff suppressed because it is too large Load Diff