Commit a8cf043d authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

Merge branch 'trac1959'

parents 0c9ad616 9a3a5db5
......@@ -1223,6 +1223,7 @@ AC_CONFIG_FILES([Makefile
tests/tools/badpacket/tests/Makefile
tests/tools/perfdhcp/Makefile
tests/tools/perfdhcp/tests/Makefile
tests/tools/perfdhcp/templates/Makefile
dns++.pc
])
AC_OUTPUT([doc/version.ent
......
......@@ -50,6 +50,15 @@ IfaceMgr::Iface::Iface(const std::string& name, int ifindex)
memset(mac_, 0, sizeof(mac_));
}
void
IfaceMgr::Iface::closeSockets() {
for (SocketCollection::iterator sock = sockets_.begin();
sock != sockets_.end(); ++sock) {
close(sock->sockfd_);
}
sockets_.clear();
}
std::string
IfaceMgr::Iface::getFullName() const {
ostringstream tmp;
......@@ -138,15 +147,8 @@ IfaceMgr::IfaceMgr()
void IfaceMgr::closeSockets() {
for (IfaceCollection::iterator iface = ifaces_.begin();
iface != ifaces_.end(); ++iface) {
for (SocketCollection::iterator sock = iface->sockets_.begin();
sock != iface->sockets_.end(); ++sock) {
cout << "Closing socket " << sock->sockfd_ << endl;
close(sock->sockfd_);
}
iface->sockets_.clear();
iface->closeSockets();
}
}
IfaceMgr::~IfaceMgr() {
......@@ -477,11 +479,34 @@ IfaceMgr::getLocalAddress(const IOAddress& remote_addr, const uint16_t port) {
asio::io_service io_service;
asio::ip::udp::socket sock(io_service);
// Try to connect to remote endpoint and check if attempt is successful.
asio::error_code err_code;
// If remote address is broadcast address we have to
// allow this on the socket.
if (remote_addr.getAddress().is_v4() &&
(remote_addr == IOAddress("255.255.255.255"))) {
// Socket has to be open prior to setting the broadcast
// option. Otherwise set_option will complain about
// bad file descriptor.
// @todo: We don't specify interface in any way here. 255.255.255.255
// We can very easily end up with a socket working on a different
// interface.
sock.open(asio::ip::udp::v4(), err_code);
if (err_code) {
isc_throw(Unexpected, "failed to open UDPv4 socket");
}
sock.set_option(asio::socket_base::broadcast(true), err_code);
if (err_code) {
sock.close();
isc_throw(Unexpected, "failed to enable broadcast on the socket");
}
}
// Try to connect to remote endpoint and check if attempt is successful.
sock.connect(remote_endpoint->getASIOEndpoint(), err_code);
if (err_code) {
isc_throw(Unexpected,"Failed to connect to remote endpoint.");
sock.close();
isc_throw(Unexpected,"failed to connect to remote endpoint.");
}
// Once we are connected socket object holds local endpoint.
......@@ -489,6 +514,9 @@ IfaceMgr::getLocalAddress(const IOAddress& remote_addr, const uint16_t port) {
sock.local_endpoint();
asio::ip::address local_address(local_endpoint.address());
// Close the socket.
sock.close();
// Return address of local endpoint.
return IOAddress(local_address);
}
......@@ -546,8 +574,9 @@ int IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port) {
memset(&addr6, 0, sizeof(addr6));
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(port);
if (addr.toText() != "::1")
addr6.sin6_scope_id = if_nametoindex(iface.getName().c_str());
if (addr.toText() != "::1") {
addr6.sin6_scope_id = if_nametoindex(iface.getName().c_str());
}
memcpy(&addr6.sin6_addr,
addr.getAddress().to_v6().to_bytes().data(),
......@@ -724,7 +753,6 @@ IfaceMgr::send(const Pkt6Ptr& pkt) {
bool
IfaceMgr::send(const Pkt4Ptr& pkt)
{
Iface* iface = getIface(pkt->getIface());
if (!iface) {
isc_throw(BadValue, "Unable to send Pkt4. Invalid interface ("
......@@ -800,8 +828,9 @@ IfaceMgr::receive4(uint32_t timeout) {
/// provided set to indicated which sockets have something to read.
for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
for (SocketCollection::const_iterator s = iface->sockets_.begin();
s != iface->sockets_.end(); ++s) {
const SocketCollection& socket_collection = iface->getSockets();
for (SocketCollection::const_iterator s = socket_collection.begin();
s != socket_collection.end(); ++s) {
// Only deal with IPv4 addresses.
if (s->addr_.getFamily() == AF_INET) {
......@@ -864,8 +893,9 @@ IfaceMgr::receive4(uint32_t timeout) {
// Let's find out which interface/socket has the data
for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
for (SocketCollection::const_iterator s = iface->sockets_.begin();
s != iface->sockets_.end(); ++s) {
const SocketCollection& socket_collection = iface->getSockets();
for (SocketCollection::const_iterator s = socket_collection.begin();
s != socket_collection.end(); ++s) {
if (FD_ISSET(s->sockfd_, &sockets)) {
candidate = &(*s);
break;
......@@ -967,9 +997,9 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout) {
/// provided set to indicated which sockets have something to read.
IfaceCollection::const_iterator iface;
for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
for (SocketCollection::const_iterator s = iface->sockets_.begin();
s != iface->sockets_.end(); ++s) {
const SocketCollection& socket_collection = iface->getSockets();
for (SocketCollection::const_iterator s = socket_collection.begin();
s != socket_collection.end(); ++s) {
// Only deal with IPv4 addresses.
if (s->addr_.getFamily() == AF_INET6) {
......@@ -1032,8 +1062,9 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout) {
// Let's find out which interface/socket has the data
for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
for (SocketCollection::const_iterator s = iface->sockets_.begin();
s != iface->sockets_.end(); ++s) {
const SocketCollection& socket_collection = iface->getSockets();
for (SocketCollection::const_iterator s = socket_collection.begin();
s != socket_collection.end(); ++s) {
if (FD_ISSET(s->sockfd_, &sockets)) {
candidate = &(*s);
break;
......@@ -1168,8 +1199,9 @@ uint16_t IfaceMgr::getSocket(const isc::dhcp::Pkt6& pkt) {
<< pkt.getIface());
}
const SocketCollection& socket_collection = iface->getSockets();
SocketCollection::const_iterator s;
for (s = iface->sockets_.begin(); s != iface->sockets_.end(); ++s) {
for (s = socket_collection.begin(); s != socket_collection.end(); ++s) {
if ((s->family_ == AF_INET6) &&
(!s->addr_.getAddress().to_v6().is_multicast())) {
return (s->sockfd_);
......@@ -1190,8 +1222,9 @@ uint16_t IfaceMgr::getSocket(isc::dhcp::Pkt4 const& pkt) {
<< pkt.getIface());
}
const SocketCollection& socket_collection = iface->getSockets();
SocketCollection::const_iterator s;
for (s = iface->sockets_.begin(); s != iface->sockets_.end(); ++s) {
for (s = socket_collection.begin(); s != socket_collection.end(); ++s) {
if (s->family_ == AF_INET) {
return (s->sockfd_);
}
......
......@@ -72,8 +72,10 @@ public:
};
/// type that holds a list of socket informations
/// @todo: Add SocketCollectionConstIter type
typedef std::list<SocketInfo> SocketCollection;
/// @brief represents a single network interface
///
/// Iface structure represents network interface with all useful
......@@ -89,6 +91,9 @@ public:
/// @param ifindex interface index (unique integer identifier)
Iface(const std::string& name, int ifindex);
/// @brief Closes all open sockets on interface.
void closeSockets();
/// @brief Returns full interface name as "ifname/ifindex" string.
///
/// @return string with interface name
......@@ -192,11 +197,25 @@ public:
/// @return true if there was such socket, false otherwise
bool delSocket(uint16_t sockfd);
/// @brief Returns collection of all sockets added to interface.
///
/// When new socket is created with @ref IfaceMgr::openSocket
/// it is added to sockets collection on particular interface.
/// If socket is opened by other means (e.g. function that does
/// not use @ref IfaceMgr::openSocket) it will not be available
/// in this collection. Note that functions like
/// @ref IfaceMgr::openSocketFromIface use
/// @ref IfaceMgr::openSocket internally.
/// The returned reference is only valid during the lifetime of
/// the IfaceMgr object that returned it.
///
/// @return collection of sockets added to interface
const SocketCollection& getSockets() const { return sockets_; }
protected:
/// socket used to sending data
/// TODO: this should be protected
SocketCollection sockets_;
protected:
/// network interface name
std::string name_;
......
......@@ -15,6 +15,7 @@
#include <boost/shared_array.hpp>
#include <boost/shared_ptr.hpp>
#include <util/buffer.h>
#include <exceptions/exceptions.h>
#include <dhcp/libdhcp++.h>
#include "config.h"
#include <dhcp/dhcp4.h>
......@@ -34,6 +35,31 @@ std::map<unsigned short, Option::Factory*> LibDHCP::v4factories_;
std::map<unsigned short, Option::Factory*> LibDHCP::v6factories_;
OptionPtr
LibDHCP::optionFactory(Option::Universe u,
uint16_t type,
const OptionBuffer& buf) {
FactoryMap::iterator it;
if (u == Option::V4) {
it = v4factories_.find(type);
if (it == v4factories_.end()) {
isc_throw(BadValue, "factory function not registered "
"for DHCP v4 option type " << type);
}
} else if (u == Option::V6) {
it = v6factories_.find(type);
if (it == v6factories_.end()) {
isc_throw(BadValue, "factory function not registered "
"for DHCPv6 option type " << type);
}
} else {
isc_throw(BadValue, "invalid universe specified (expected "
"Option::V4 or Option::V6");
}
return (it->second(u, type, buf));
}
size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
isc::dhcp::Option::OptionCollection& options) {
size_t offset = 0;
......
......@@ -25,6 +25,26 @@ namespace dhcp {
class LibDHCP {
public:
/// Map of factory functions.
typedef std::map<unsigned short, Option::Factory*> FactoryMap;
/// @brief Factory function to create instance of option.
///
/// Factory method creates instance of specified option. The option
/// to be created has to have corresponding factory function
/// registered with \ref LibDHCP::OptionFactoryRegister.
///
/// @param u universe of the option (V4 or V6)
/// @param type option-type
/// @param buf option-buffer
/// @throw isc::InvalidOperation if there is no factory function
/// registered for specified option type.
/// @return instance of option.
static isc::dhcp::OptionPtr optionFactory(isc::dhcp::Option::Universe u,
uint16_t type,
const OptionBuffer& buf);
/// Builds collection of options.
///
/// Builds raw (on-wire) data for provided collection of options.
......@@ -84,10 +104,10 @@ public:
Option::Factory * factory);
protected:
/// pointers to factories that produce DHCPv6 options
static std::map<unsigned short, Option::Factory*> v4factories_;
static FactoryMap v4factories_;
/// pointers to factories that produce DHCPv6 options
static std::map<unsigned short, Option::Factory*> v6factories_;
static FactoryMap v6factories_;
};
}
......
......@@ -29,6 +29,14 @@ using namespace isc::util;
namespace isc {
namespace dhcp {
OptionPtr
Option::factory(Option::Universe u,
uint16_t type,
const OptionBuffer& buf) {
return(LibDHCP::optionFactory(u, type, buf));
}
Option::Option(Universe u, uint16_t type)
:universe_(u), type_(type) {
......
......@@ -63,9 +63,45 @@ public:
/// @param type option type
/// @param buf pointer to a buffer
///
/// @todo Passing a separate buffer for each option means that a copy
/// was done. We can avoid it by passing 2 iterators.
///
/// @return a pointer to a created option object
typedef OptionPtr Factory(Option::Universe u, uint16_t type, const OptionBuffer& buf);
/// @brief Factory function to create instance of option.
///
/// Factory method creates instance of specified option. The option
/// to be created has to have corresponding factory function
/// registered with \ref LibDHCP::OptionFactoryRegister.
///
/// @param u universe of the option (V4 or V6)
/// @param type option-type
/// @param buf option-buffer
/// @throw isc::InvalidOperation if there is no factory function
/// registered for specified option type.
/// @return instance of option.
static OptionPtr factory(Option::Universe u,
uint16_t type,
const OptionBuffer& buf);
/// @brief Factory function to create instance of option.
///
/// Factory method creates instance of specified option. The option
/// to be created has to have corresponding factory function
/// registered with \ref LibDHCP::OptionFactoryRegister.
/// This method creates empty \ref OptionBuffer object. Use this
/// factory function if it is not needed to pass custom buffer.
///
/// @param u universe of the option (V4 or V6)
/// @param type option-type
/// @throw isc::InvalidOperation if there is no factory function
/// registered for specified option type.
/// @return instance of option.
static OptionPtr factory(Option::Universe u, uint16_t type) {
return factory(u, type, OptionBuffer());
}
/// @brief ctor, used for options constructed, usually during transmission
///
/// @param u option universe (DHCPv4 or DHCPv6)
......
......@@ -59,6 +59,7 @@ public:
~IfaceMgrTest() {
}
};
// We need some known interface to work reliably. Loopback interface
......@@ -217,6 +218,94 @@ TEST_F(IfaceMgrTest, getIface) {
}
TEST_F(IfaceMgrTest, multipleSockets) {
boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
// container for initialized socket descriptors
std::list<uint16_t> init_sockets;
// create socket #1
int socket1 = 0;
ASSERT_NO_THROW(
socket1 = ifacemgr->openSocketFromIface(LOOPBACK, PORT1, AF_INET);
);
ASSERT_GT(socket1, 0);
init_sockets.push_back(socket1);
// create socket #2
IOAddress loAddr("127.0.0.1");
int socket2 = 0;
ASSERT_NO_THROW(
socket2 = ifacemgr->openSocketFromRemoteAddress(loAddr, PORT2);
);
ASSERT_GT(socket2, 0);
init_sockets.push_back(socket2);
// Get loopback interface. If we don't find one we are unable to run
// this test but we don't want to fail.
IfaceMgr::Iface* iface_ptr = ifacemgr->getIface(LOOPBACK);
if (iface_ptr == NULL) {
cout << "Local loopback interface not found. Skipping test. " << endl;
return;
}
// Once sockets have been sucessfully opened, they are supposed to
// be on the list. Here we start to test if all expected sockets
// are on the list and no other (unexpected) socket is there.
IfaceMgr::SocketCollection sockets = iface_ptr->getSockets();
int matched_sockets = 0;
for (std::list<uint16_t>::iterator init_sockets_it =
init_sockets.begin();
init_sockets_it != init_sockets.end(); ++init_sockets_it) {
// Set socket descriptors non blocking in order to be able
// to call recv() on them without hang.
int flags = fcntl(*init_sockets_it, F_GETFL, 0);
ASSERT_GE(flags, 0);
ASSERT_GE(fcntl(*init_sockets_it, F_SETFL, flags | O_NONBLOCK), 0);
// recv() is expected to result in EWOULDBLOCK error on non-blocking
// socket in case socket is valid but simply no data are coming in.
char buf;
recv(*init_sockets_it, &buf, 1, MSG_PEEK);
EXPECT_EQ(EWOULDBLOCK, errno);
// Apart from the ability to use the socket we want to make
// sure that socket on the list is the one that we created.
for (IfaceMgr::SocketCollection::const_iterator socket_it =
sockets.begin(); socket_it != sockets.end(); ++socket_it) {
if (*init_sockets_it == socket_it->sockfd_) {
// This socket is the one that we created.
++matched_sockets;
break;
}
}
}
// all created sockets have been matched if this condition works.
EXPECT_EQ(sockets.size(), matched_sockets);
// closeSockets() is the other function that we want to test. It
// is supposed to close all sockets so as we will not be able to use
// them anymore communication.
ifacemgr->closeSockets();
// closed sockets are supposed to be removed from the list
sockets = iface_ptr->getSockets();
ASSERT_EQ(0, sockets.size());
// We are still in posession of socket descriptors that we created
// on the beginning of this test. We can use them to check whether
// closeSockets() only removed them from the list or they have been
// really closed.
for (std::list<uint16_t>::const_iterator init_sockets_it =
init_sockets.begin();
init_sockets_it != init_sockets.end(); ++init_sockets_it) {
// recv() must result in error when using invalid socket.
char buf;
recv(*init_sockets_it, &buf, 1, MSG_PEEK);
// EWOULDBLOCK would mean that socket is valid/open but
// simply no data is received so we have to check for
// other errors.
EXPECT_NE(EWOULDBLOCK, errno);
}
}
TEST_F(IfaceMgrTest, sockets6) {
// testing socket operation in a portable way is tricky
// without interface detection implemented
......@@ -317,6 +406,21 @@ TEST_F(IfaceMgrTest, socketsFromRemoteAddress) {
);
EXPECT_GT(socket2, 0);
close(socket2);
// The following test is currently disabled for OSes other than
// Linux because interface detection is not implemented on them.
// @todo enable this test for all OSes once interface detection
// is implemented.
#if defined(OS_LINUX)
// Open v4 socket to connect to broadcast address.
int socket3 = 0;
IOAddress bcastAddr("255.255.255.255");
EXPECT_NO_THROW(
socket3 = ifacemgr->openSocketFromRemoteAddress(bcastAddr, PORT2);
);
EXPECT_GT(socket3, 0);
close(socket3);
#endif
}
// TODO: disabled due to other naming on various systems
......
......@@ -18,6 +18,8 @@
#include <arpa/inet.h>
#include <gtest/gtest.h>
#include <util/buffer.h>
#include <dhcp/dhcp4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/libdhcp++.h>
#include "config.h"
......@@ -31,6 +33,19 @@ class LibDhcpTest : public ::testing::Test {
public:
LibDhcpTest() {
}
/// @brief Generic factory function to create any option.
///
/// Generic factory function to create any option.
///
/// @param u universe (V4 or V6)
/// @param type option-type
/// @param buf option-buffer
static OptionPtr genericOptionFactory(Option::Universe u, uint16_t type,
const OptionBuffer& buf) {
Option* option = new Option(u, type, buf);
return OptionPtr(option);
}
};
static const uint8_t packed[] = {
......@@ -41,6 +56,78 @@ static const uint8_t packed[] = {
1, 1, 0, 1, 114 // opt5 (5 bytes)
};
TEST(LibDhcpTest, optionFactory) {
OptionBuffer buf;
// Factory functions for specific options must be registered before
// they can be used to create options instances. Otherwise exception
// is rised.
EXPECT_THROW(LibDHCP::optionFactory(Option::V4, DHO_SUBNET_MASK, buf),
isc::BadValue);
// Let's register some factory functions (two v4 and one v6 function).
// Registration may trigger exception if function for the specified
// option has been registered already.
ASSERT_NO_THROW(
LibDHCP::OptionFactoryRegister(Option::V4, DHO_SUBNET_MASK,
&LibDhcpTest::genericOptionFactory);
);
ASSERT_NO_THROW(
LibDHCP::OptionFactoryRegister(Option::V4, DHO_TIME_OFFSET,
&LibDhcpTest::genericOptionFactory);
);
ASSERT_NO_THROW(
LibDHCP::OptionFactoryRegister(Option::V6, D6O_CLIENTID,
&LibDhcpTest::genericOptionFactory);
);
// Invoke factory functions for all options (check if registration
// was successful).
OptionPtr opt_subnet_mask;
opt_subnet_mask = LibDHCP::optionFactory(Option::V4,
DHO_SUBNET_MASK,
buf);
// Check if non-NULL DHO_SUBNET_MASK option pointer has been returned.
ASSERT_TRUE(opt_subnet_mask);
// Validate if type and universe is correct.
EXPECT_EQ(Option::V4, opt_subnet_mask->getUniverse());
EXPECT_EQ(DHO_SUBNET_MASK, opt_subnet_mask->getType());
// Expect that option does not have content..
EXPECT_EQ(0, opt_subnet_mask->len() - opt_subnet_mask->getHeaderLen());
// Fill the time offset buffer with 4 bytes of data. Each byte set to 1.
OptionBuffer time_offset_buf(4, 1);
OptionPtr opt_time_offset;
opt_time_offset = LibDHCP::optionFactory(Option::V4,
DHO_TIME_OFFSET,
time_offset_buf);
// Check if non-NULL DHO_TIME_OFFSET option pointer has been returned.
ASSERT_TRUE(opt_time_offset);
// Validate if option length, type and universe is correct.
EXPECT_EQ(Option::V4, opt_time_offset->getUniverse());
EXPECT_EQ(DHO_TIME_OFFSET, opt_time_offset->getType());
EXPECT_EQ(time_offset_buf.size(),
opt_time_offset->len() - opt_time_offset->getHeaderLen());
// Validate data in the option.
EXPECT_TRUE(std::equal(time_offset_buf.begin(), time_offset_buf.end(),
opt_time_offset->getData().begin()));
// Fill the client id buffer with 20 bytes of data. Each byte set to 2.
OptionBuffer clientid_buf(20, 2);
OptionPtr opt_clientid;
opt_clientid = LibDHCP::optionFactory(Option::V6,
D6O_CLIENTID,
clientid_buf);
// Check if non-NULL D6O_CLIENTID option pointer has been returned.
ASSERT_TRUE(opt_clientid);
// Validate if option length, type and universe is correct.
EXPECT_EQ(Option::V6, opt_clientid->getUniverse());
EXPECT_EQ(D6O_CLIENTID, opt_clientid->getType());
EXPECT_EQ(clientid_buf.size(), opt_clientid->len() - opt_clientid->getHeaderLen());
// Validate data in the option.
EXPECT_TRUE(std::equal(clientid_buf.begin(), clientid_buf.end(),
opt_clientid->getData().begin()));
}
TEST(LibDhcpTest, packOptions6) {
OptionBuffer buf(512);
isc::dhcp::Option::OptionCollection opts; // list of options
......
SUBDIRS = . tests
SUBDIRS = . tests templates
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
......@@ -18,25 +18,27 @@ if USE_STATIC_LINK
AM_LDFLAGS += -static
endif
lib_LTLIBRARIES = libb10_perfdhcp++.la
libb10_perfdhcp___la_SOURCES = command_options.cc command_options.h
libb10_perfdhcp___la_SOURCES += localized_option.h