mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-30 13:37:55 +00:00
[master] Merge branch 'trac2765'
This commit is contained in:
@@ -67,6 +67,7 @@
|
||||
* - @subpage libdhcpIntro
|
||||
* - @subpage libdhcpRelay
|
||||
* - @subpage libdhcpIfaceMgr
|
||||
* - @subpage libdhcpPktFilter
|
||||
* - @subpage libdhcpsrv
|
||||
* - @subpage leasemgr
|
||||
* - @subpage cfgmgr
|
||||
|
@@ -175,6 +175,10 @@ IPv4 DHCP server but it is not running.
|
||||
A debug message issued during startup, this indicates that the IPv4 DHCP
|
||||
server is about to open sockets on the specified port.
|
||||
|
||||
% DHCP4_OPEN_SOCKET_FAIL failed to create socket: %1
|
||||
A warning message issued when IfaceMgr fails to open and bind a socket. The reason
|
||||
for the failure is appended as an argument of the log message.
|
||||
|
||||
% DHCP4_PACKET_PARSE_FAIL failed to parse incoming packet: %1
|
||||
The IPv4 DHCP server has received a packet that it is unable to
|
||||
interpret. The reason why the packet is invalid is included in the message.
|
||||
|
@@ -140,10 +140,15 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
|
||||
// will be able to respond directly.
|
||||
IfaceMgr::instance().setMatchingPacketFilter(direct_response_desired);
|
||||
|
||||
// Open sockets only if port is non-zero. Port 0 is used
|
||||
// for non-socket related testing.
|
||||
if (port) {
|
||||
// open sockets only if port is non-zero. Port 0 is used
|
||||
// for non-socket related testing.
|
||||
IfaceMgr::instance().openSockets4(port_, use_bcast_);
|
||||
// Create error handler. This handler will be called every time
|
||||
// the socket opening operation fails. We use this handler to
|
||||
// log a warning.
|
||||
isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
|
||||
boost::bind(&Dhcpv4Srv::ifaceMgrSocket4ErrorHandler, _1);
|
||||
IfaceMgr::instance().openSockets4(port_, use_bcast_, error_handler);
|
||||
}
|
||||
|
||||
string srvid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_ID_FILE);
|
||||
@@ -1674,7 +1679,9 @@ Dhcpv4Srv::openActiveSockets(const uint16_t port,
|
||||
// sockets are marked active or inactive.
|
||||
// @todo Optimization: we should not reopen all sockets but rather select
|
||||
// those that have been affected by the new configuration.
|
||||
if (!IfaceMgr::instance().openSockets4(port, use_bcast)) {
|
||||
isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
|
||||
boost::bind(&Dhcpv4Srv::ifaceMgrSocket4ErrorHandler, _1);
|
||||
if (!IfaceMgr::instance().openSockets4(port, use_bcast, error_handler)) {
|
||||
LOG_WARN(dhcp4_logger, DHCP4_NO_SOCKETS_OPEN);
|
||||
}
|
||||
}
|
||||
@@ -1768,6 +1775,11 @@ Dhcpv4Srv::unpackOptions(const OptionBuffer& buf,
|
||||
return (offset);
|
||||
}
|
||||
|
||||
void
|
||||
Dhcpv4Srv::ifaceMgrSocket4ErrorHandler(const std::string& errmsg) {
|
||||
// Log the reason for socket opening failure and return.
|
||||
LOG_WARN(dhcp4_logger, DHCP4_OPEN_SOCKET_FAIL).arg(errmsg);
|
||||
}
|
||||
|
||||
} // namespace dhcp
|
||||
} // namespace isc
|
||||
|
@@ -525,6 +525,15 @@ private:
|
||||
/// @return Option that contains netmask information
|
||||
static OptionPtr getNetmaskOption(const Subnet4Ptr& subnet);
|
||||
|
||||
/// @brief Implements the error handler for socket open failure.
|
||||
///
|
||||
/// This callback function is installed on the @c isc::dhcp::IfaceMgr
|
||||
/// when IPv4 sockets are being open. When socket fails to open for
|
||||
/// any reason, this function is called. It simply logs the error message.
|
||||
///
|
||||
/// @param errmsg An error message containing a cause of the failure.
|
||||
static void ifaceMgrSocket4ErrorHandler(const std::string& errmsg);
|
||||
|
||||
/// @brief Allocation Engine.
|
||||
/// Pointer to the allocation engine that we are currently using
|
||||
/// It must be a pointer, because we will support changing engines
|
||||
|
@@ -89,6 +89,26 @@ TEST_F(Dhcpv4SrvTest, basic) {
|
||||
EXPECT_TRUE(naked_srv->getServerID());
|
||||
}
|
||||
|
||||
// This test verifies that exception is not thrown when an error occurs during
|
||||
// opening sockets. This test forces an error by adding a fictious interface
|
||||
// to the IfaceMgr. An attempt to open socket on this interface must always
|
||||
// fail. The DHCPv4 installs the error handler function to prevent exceptions
|
||||
// being thrown from the openSockets4 function.
|
||||
// @todo The server tests for socket should be extended but currently the
|
||||
// ability to unit test the sockets code is somewhat limited.
|
||||
TEST_F(Dhcpv4SrvTest, openActiveSockets) {
|
||||
ASSERT_NO_THROW(CfgMgr::instance().activateAllIfaces());
|
||||
|
||||
Iface iface("bogusiface", 255);
|
||||
iface.flag_loopback_ = false;
|
||||
iface.flag_up_ = true;
|
||||
iface.flag_running_ = true;
|
||||
iface.inactive4_ = false;
|
||||
iface.addAddress(IOAddress("192.0.0.0"));
|
||||
IfaceMgr::instance().addInterface(iface);
|
||||
ASSERT_NO_THROW(Dhcpv4Srv::openActiveSockets(DHCP4_SERVER_PORT, false));
|
||||
}
|
||||
|
||||
// This test verifies that the destination address of the response
|
||||
// message is set to giaddr, when giaddr is set to non-zero address
|
||||
// in the received message.
|
||||
|
@@ -44,6 +44,7 @@ Dhcpv4SrvTest::Dhcpv4SrvTest()
|
||||
pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110")));
|
||||
subnet_->addPool(pool_);
|
||||
|
||||
CfgMgr::instance().deleteActiveIfaces();
|
||||
CfgMgr::instance().deleteSubnets4();
|
||||
CfgMgr::instance().addSubnet4(subnet_);
|
||||
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#define DHCP4_TEST_UTILS_H
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <dhcp/iface_mgr.h>
|
||||
#include <dhcp/pkt4.h>
|
||||
#include <dhcp/pkt_filter.h>
|
||||
#include <dhcp/pkt_filter_inet.h>
|
||||
@@ -52,9 +53,10 @@ public:
|
||||
}
|
||||
|
||||
/// Does nothing.
|
||||
virtual int openSocket(const Iface&, const isc::asiolink::IOAddress&,
|
||||
const uint16_t, const bool, const bool) {
|
||||
return (0);
|
||||
virtual SocketInfo openSocket(const Iface&,
|
||||
const isc::asiolink::IOAddress& addr,
|
||||
const uint16_t port, const bool, const bool) {
|
||||
return (SocketInfo(addr, port, 0));
|
||||
}
|
||||
|
||||
/// Does nothing.
|
||||
|
@@ -41,7 +41,7 @@ libb10_dhcp___la_SOURCES += option_string.cc option_string.h
|
||||
libb10_dhcp___la_SOURCES += protocol_util.cc protocol_util.h
|
||||
libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
|
||||
libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
|
||||
libb10_dhcp___la_SOURCES += pkt_filter.h
|
||||
libb10_dhcp___la_SOURCES += pkt_filter.h pkt_filter.cc
|
||||
libb10_dhcp___la_SOURCES += pkt_filter_inet.cc pkt_filter_inet.h
|
||||
|
||||
if OS_LINUX
|
||||
|
@@ -88,6 +88,10 @@ Iface::closeSockets(const uint16_t family) {
|
||||
// Close and delete the socket and move to the
|
||||
// next one.
|
||||
close(sock->sockfd_);
|
||||
// Close fallback socket if open.
|
||||
if (sock->fallbackfd_ >= 0) {
|
||||
close(sock->fallbackfd_);
|
||||
}
|
||||
sockets_.erase(sock++);
|
||||
|
||||
} else {
|
||||
@@ -148,6 +152,10 @@ bool Iface::delSocket(uint16_t sockfd) {
|
||||
while (sock!=sockets_.end()) {
|
||||
if (sock->sockfd_ == sockfd) {
|
||||
close(sockfd);
|
||||
// Close fallback socket if open.
|
||||
if (sock->fallbackfd_ >= 0) {
|
||||
close(sock->fallbackfd_);
|
||||
}
|
||||
sockets_.erase(sock);
|
||||
return (true); //socket found
|
||||
}
|
||||
@@ -284,8 +292,9 @@ void IfaceMgr::stubDetectIfaces() {
|
||||
addInterface(iface);
|
||||
}
|
||||
|
||||
bool IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast) {
|
||||
int sock;
|
||||
bool
|
||||
IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast,
|
||||
IfaceMgrErrorMsgCallback error_handler) {
|
||||
int count = 0;
|
||||
|
||||
// This option is used to bind sockets to particular interfaces.
|
||||
@@ -322,6 +331,7 @@ bool IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int sock = -1;
|
||||
// If selected interface is broadcast capable set appropriate
|
||||
// options on the socket so as it can receive and send broadcast
|
||||
// messages.
|
||||
@@ -331,36 +341,53 @@ bool IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast) {
|
||||
// bind to INADDR_ANY address but we can do it only once. Thus,
|
||||
// if one socket has been bound we can't do it any further.
|
||||
if (!bind_to_device && bcast_num > 0) {
|
||||
isc_throw(SocketConfigError, "SO_BINDTODEVICE socket option is"
|
||||
<< " not supported on this OS; therefore, DHCP"
|
||||
<< " server can only listen broadcast traffic on"
|
||||
<< " a single interface");
|
||||
handleSocketConfigError("SO_BINDTODEVICE socket option is"
|
||||
" not supported on this OS;"
|
||||
" therefore, DHCP server can only"
|
||||
" listen broadcast traffic on a"
|
||||
" single interface",
|
||||
error_handler);
|
||||
continue;
|
||||
|
||||
} else {
|
||||
// We haven't open any broadcast sockets yet, so we can
|
||||
// open at least one more.
|
||||
sock = openSocket(iface->getName(), *addr, port, true, true);
|
||||
// Binding socket to an interface is not supported so we can't
|
||||
// open any more broadcast sockets. Increase the number of
|
||||
// opened broadcast sockets.
|
||||
try {
|
||||
// We haven't open any broadcast sockets yet, so we can
|
||||
// open at least one more.
|
||||
sock = openSocket(iface->getName(), *addr, port,
|
||||
true, true);
|
||||
} catch (const Exception& ex) {
|
||||
handleSocketConfigError(ex.what(), error_handler);
|
||||
continue;
|
||||
|
||||
}
|
||||
// Binding socket to an interface is not supported so we
|
||||
// can't open any more broadcast sockets. Increase the
|
||||
// number of open broadcast sockets.
|
||||
if (!bind_to_device) {
|
||||
++bcast_num;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Not broadcast capable, do not set broadcast flags.
|
||||
sock = openSocket(iface->getName(), *addr, port, false, false);
|
||||
try {
|
||||
// Not broadcast capable, do not set broadcast flags.
|
||||
sock = openSocket(iface->getName(), *addr, port,
|
||||
false, false);
|
||||
} catch (const Exception& ex) {
|
||||
handleSocketConfigError(ex.what(), error_handler);
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
if (sock < 0) {
|
||||
const char* errstr = strerror(errno);
|
||||
isc_throw(SocketConfigError, "failed to open IPv4 socket"
|
||||
<< " supporting broadcast traffic, reason:"
|
||||
<< errstr);
|
||||
handleSocketConfigError(std::string("failed to open IPv4 socket,"
|
||||
" reason:") + errstr,
|
||||
error_handler);
|
||||
} else {
|
||||
++count;
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return (count > 0);
|
||||
@@ -459,6 +486,21 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
|
||||
return (count > 0);
|
||||
}
|
||||
|
||||
void
|
||||
IfaceMgr::handleSocketConfigError(const std::string& errmsg,
|
||||
IfaceMgrErrorMsgCallback handler) {
|
||||
// If error handler is installed, we don't want to throw an exception, but
|
||||
// rather call this handler.
|
||||
if (handler != NULL) {
|
||||
handler(errmsg);
|
||||
|
||||
} else {
|
||||
isc_throw(SocketConfigError, errmsg);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
|
||||
for (IfaceCollection::const_iterator iface=ifaces_.begin();
|
||||
@@ -741,7 +783,7 @@ int IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port) {
|
||||
}
|
||||
}
|
||||
|
||||
SocketInfo info(sock, addr, port);
|
||||
SocketInfo info(addr, port, sock);
|
||||
iface.addSocket(info);
|
||||
|
||||
return (sock);
|
||||
@@ -754,13 +796,11 @@ int IfaceMgr::openSocket4(Iface& iface, const IOAddress& addr,
|
||||
// Skip checking if the packet_filter_ is non-NULL because this check
|
||||
// has been already done when packet filter object was set.
|
||||
|
||||
int sock = packet_filter_->openSocket(iface, addr, port,
|
||||
receive_bcast, send_bcast);
|
||||
|
||||
SocketInfo info(sock, addr, port);
|
||||
SocketInfo info = packet_filter_->openSocket(iface, addr, port,
|
||||
receive_bcast, send_bcast);
|
||||
iface.addSocket(info);
|
||||
|
||||
return (sock);
|
||||
return (info.sockfd_);
|
||||
}
|
||||
|
||||
bool
|
||||
|
@@ -22,6 +22,7 @@
|
||||
#include <dhcp/pkt6.h>
|
||||
#include <dhcp/pkt_filter.h>
|
||||
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <boost/scoped_array.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
@@ -72,19 +73,49 @@ public:
|
||||
|
||||
/// Holds information about socket.
|
||||
struct SocketInfo {
|
||||
uint16_t sockfd_; /// socket descriptor
|
||||
|
||||
isc::asiolink::IOAddress addr_; /// bound address
|
||||
uint16_t port_; /// socket port
|
||||
uint16_t family_; /// IPv4 or IPv6
|
||||
|
||||
/// @brief Socket descriptor (a.k.a. primary socket).
|
||||
int sockfd_;
|
||||
|
||||
/// @brief Fallback socket descriptor.
|
||||
///
|
||||
/// This socket descriptor holds the handle to the fallback socket.
|
||||
/// The fallback socket is created when there is a need for the regular
|
||||
/// datagram socket to be bound to an IP address and port, besides
|
||||
/// primary socket (sockfd_) which is actually used to receive and process
|
||||
/// the DHCP messages. The fallback socket (if exists) is always associated
|
||||
/// with the primary socket. In particular, the need for the fallback socket
|
||||
/// arises when raw socket is a primary one. When primary socket is open,
|
||||
/// it is bound to an interface not the address and port. The implications
|
||||
/// include the possibility that the other process (e.g. the other instance
|
||||
/// of DHCP server) will bind to the same address and port through which the
|
||||
/// raw socket receives the DHCP messages.Another implication is that the
|
||||
/// kernel, being unaware of the DHCP server operating through the raw
|
||||
/// socket, will respond with the ICMP "Destination port unreachable"
|
||||
/// messages when DHCP messages are only received through the raw socket.
|
||||
/// In order to workaround the issues mentioned here, the fallback socket
|
||||
/// should be opened so as/ the kernel is aware that the certain address
|
||||
/// and port is in use.
|
||||
///
|
||||
/// The fallback description is supposed to be set to a negative value if
|
||||
/// the fallback socket is closed (not open).
|
||||
int fallbackfd_;
|
||||
|
||||
/// @brief SocketInfo constructor.
|
||||
///
|
||||
/// @param sockfd socket descriptor
|
||||
/// @param addr an address the socket is bound to
|
||||
/// @param port a port the socket is bound to
|
||||
SocketInfo(uint16_t sockfd, const isc::asiolink::IOAddress& addr,
|
||||
uint16_t port)
|
||||
:sockfd_(sockfd), addr_(addr), port_(port), family_(addr.getFamily()) { }
|
||||
/// @param addr An address the socket is bound to.
|
||||
/// @param port A port the socket is bound to.
|
||||
/// @param sockfd Socket descriptor.
|
||||
/// @param fallbackfd A descriptor of the fallback socket.
|
||||
SocketInfo(const isc::asiolink::IOAddress& addr, const uint16_t port,
|
||||
const int sockfd, const int fallbackfd = -1)
|
||||
: addr_(addr), port_(port), family_(addr.getFamily()),
|
||||
sockfd_(sockfd), fallbackfd_(fallbackfd) { }
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -343,6 +374,13 @@ public:
|
||||
bool inactive6_;
|
||||
};
|
||||
|
||||
/// @brief This type describes the callback function invoked when error occurs
|
||||
/// in the IfaceMgr.
|
||||
///
|
||||
/// @param errmsg An error message.
|
||||
typedef
|
||||
boost::function<void(const std::string& errmsg)> IfaceMgrErrorMsgCallback;
|
||||
|
||||
/// @brief Handles network interfaces, transmission and reception.
|
||||
///
|
||||
/// IfaceMgr is an interface manager class that detects available network
|
||||
@@ -386,7 +424,7 @@ public:
|
||||
/// @return true if direct response is supported.
|
||||
bool isDirectResponseSupported() const;
|
||||
|
||||
/// @brief Returns interface with specified interface index
|
||||
/// @brief Returns interfac specified interface index
|
||||
///
|
||||
/// @param ifindex index of searched interface
|
||||
///
|
||||
@@ -579,9 +617,20 @@ public:
|
||||
const uint16_t port);
|
||||
|
||||
|
||||
/// Opens IPv6 sockets on detected interfaces.
|
||||
/// @brief Opens IPv6 sockets on detected interfaces.
|
||||
///
|
||||
/// Will throw exception if socket creation fails.
|
||||
/// @todo This function will throw an exception immediately when a socket
|
||||
/// fails to open. This is undersired behavior because it will preclude
|
||||
/// other sockets from opening. We should strive to provide similar mechanism
|
||||
/// that has been introduced for V4 sockets. If socket creation fails the
|
||||
/// appropriate error handler is called and once the handler returns the
|
||||
/// function contnues to open other sockets. The change in the IfaceMgr
|
||||
/// is quite straight forward and it is proven to work for V4. However,
|
||||
/// unit testing it is a bit involved, because for unit testing we need
|
||||
/// a replacement of the openSocket6 function which will mimic the
|
||||
/// behavior of the real socket opening. For the V4 we have the means to
|
||||
/// to achieve that with the replaceable PktFilter class. For V6, the
|
||||
/// implementation is hardcoded in the openSocket6.
|
||||
///
|
||||
/// @param port specifies port number (usually DHCP6_SERVER_PORT)
|
||||
///
|
||||
@@ -589,16 +638,71 @@ public:
|
||||
/// @return true if any sockets were open
|
||||
bool openSockets6(const uint16_t port = DHCP6_SERVER_PORT);
|
||||
|
||||
/// Opens IPv4 sockets on detected interfaces.
|
||||
/// Will throw exception if socket creation fails.
|
||||
/// @brief Opens IPv4 sockets on detected interfaces.
|
||||
///
|
||||
/// This function attempts to open sockets on all interfaces which have been
|
||||
/// detected by @c IfaceMgr and meet the following conditions:
|
||||
/// - interface is not a local loopback,
|
||||
/// - interface is running (connected),
|
||||
/// - interface is up,
|
||||
/// - interface is active, e.g. selected from the configuration to be used
|
||||
/// to listen DHCPv4 messages,
|
||||
/// - interface has an IPv4 address assigned.
|
||||
///
|
||||
/// The type of the socket being open depends on the selected Packet Filter
|
||||
/// represented by a class derived from @c isc::dhcp::PktFilter abstract
|
||||
/// class.
|
||||
///
|
||||
/// It is possible to specify whether sockets should be broadcast capable.
|
||||
/// In most of the cases, the sockets should support broadcast traffic, e.g.
|
||||
/// DHCPv4 server and relay need to listen to broadcast messages sent by
|
||||
/// clients. If the socket has to be open on the particular interface, this
|
||||
/// interface must have broadcast flag set. If this condition is not met,
|
||||
/// the socket will be created in the unicast-only mode. If there are
|
||||
/// multiple broadcast-capable interfaces present, they may be all open
|
||||
/// in a broadcast mode only if the OS supports SO_BINDTODEVICE (bind socket
|
||||
/// to a device) socket option. If this option is not supported, only the
|
||||
/// first broadcast-capable socket will be opened in the broadcast mode.
|
||||
/// The error will be reported for sockets being opened on other interfaces.
|
||||
/// If the socket is bound to a device (interface), the broadcast traffic
|
||||
/// sent to this interface will be received on this interface only.
|
||||
/// This allows the DHCPv4 server or relay to detect the interface on which
|
||||
/// the broadcast message has been received. This interface is later used
|
||||
/// to send a response.
|
||||
///
|
||||
/// On the systems with multiple interfaces, it is often desired that the
|
||||
/// failure to open a socket on a particular interface doesn't cause a
|
||||
/// fatal error and sockets should be opened on remaining interfaces.
|
||||
/// However, the warning about the failure for the particular socket should
|
||||
/// be communicated to the caller. The libdhcp++ is a common library with
|
||||
/// no logger associated with it. Most of the functions in this library
|
||||
/// communicate errors via exceptions. In case of openSockets4 function
|
||||
/// exception must not be thrown if the function is supposed to continue
|
||||
/// opening sockets, despite an error. Therefore, if such a behavior is
|
||||
/// desired, the error handler function can be passed as a parameter.
|
||||
/// This error handler is called (if present) with an error string.
|
||||
/// Typically, error handler will simply log an error using an application
|
||||
/// logger, but it can do more sophisticated error handling too.
|
||||
///
|
||||
/// @todo It is possible that additional parameters will have to be added
|
||||
/// to the error handler, e.g. Iface if it was really supposed to do
|
||||
/// some more sophisticated error handling.
|
||||
///
|
||||
/// If the error handler is not installed (is NULL), the exception is thrown
|
||||
/// for each failure (default behavior).
|
||||
///
|
||||
/// @param port specifies port number (usually DHCP4_SERVER_PORT)
|
||||
/// @param use_bcast configure sockets to support broadcast messages.
|
||||
/// @param error_handler A pointer to an error handler function which is
|
||||
/// called by the openSockets4 when it fails to open a socket. This
|
||||
/// parameter can be NULL to indicate that the callback should not be used.
|
||||
///
|
||||
/// @throw SocketOpenFailure if tried and failed to open socket.
|
||||
/// @throw SocketOpenFailure if tried and failed to open socket and callback
|
||||
/// function hasn't been specified.
|
||||
/// @return true if any sockets were open
|
||||
bool openSockets4(const uint16_t port = DHCP4_SERVER_PORT,
|
||||
const bool use_bcast = true);
|
||||
const bool use_bcast = true,
|
||||
IfaceMgrErrorMsgCallback error_handler = NULL);
|
||||
|
||||
/// @brief Closes all open sockets.
|
||||
/// Is used in destructor, but also from Dhcpv4Srv and Dhcpv6Srv classes.
|
||||
@@ -676,6 +780,15 @@ public:
|
||||
/// not having address assigned.
|
||||
void setMatchingPacketFilter(const bool direct_response_desired = false);
|
||||
|
||||
/// @brief Adds an interface to list of known interfaces.
|
||||
///
|
||||
/// @param iface reference to Iface object.
|
||||
/// @note This function must be public because it has to be callable
|
||||
/// from unit tests.
|
||||
void addInterface(const Iface& iface) {
|
||||
ifaces_.push_back(iface);
|
||||
}
|
||||
|
||||
/// A value of socket descriptor representing "not specified" state.
|
||||
static const int INVALID_SOCKET = -1;
|
||||
|
||||
@@ -720,13 +833,6 @@ protected:
|
||||
/// @return socket descriptor
|
||||
int openSocket6(Iface& iface, const isc::asiolink::IOAddress& addr, uint16_t port);
|
||||
|
||||
/// @brief Adds an interface to list of known interfaces.
|
||||
///
|
||||
/// @param iface reference to Iface object.
|
||||
void addInterface(const Iface& iface) {
|
||||
ifaces_.push_back(iface);
|
||||
}
|
||||
|
||||
/// @brief Detects network interfaces.
|
||||
///
|
||||
/// This method will eventually detect available interfaces. For now
|
||||
@@ -825,6 +931,23 @@ private:
|
||||
getLocalAddress(const isc::asiolink::IOAddress& remote_addr,
|
||||
const uint16_t port);
|
||||
|
||||
/// @brief Handles an error which occurs during configuration of a socket.
|
||||
///
|
||||
/// If the handler callback is specified (non-NULL), this handler is
|
||||
/// called and the specified error message is passed to it. If the
|
||||
/// handler is not specified, the @c isc::dhcpSocketConfigError exception
|
||||
/// is thrown with the specified message.
|
||||
///
|
||||
/// This function should be called to handle errors which occur during
|
||||
/// socket opening, binding or configuration (e.g. setting socket options
|
||||
/// etc).
|
||||
///
|
||||
/// @param errmsg An error message to be passed to a handlder function or
|
||||
/// to the @c isc::dhcp::SocketConfigError exception.
|
||||
/// @param handler An error handler function or NULL.
|
||||
void handleSocketConfigError(const std::string& errmsg,
|
||||
IfaceMgrErrorMsgCallback handler);
|
||||
|
||||
/// Holds instance of a class derived from PktFilter, used by the
|
||||
/// IfaceMgr to open sockets and send/receive packets through these
|
||||
/// sockets. It is possible to supply custom object using
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
||||
// Copyright (C) 2012-2013 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
|
||||
@@ -137,4 +137,52 @@ Another useful methods are dedicated to transmission
|
||||
Note that receive4() and receive6() methods may return NULL, e.g.
|
||||
when timeout is reached or if dhcp daemon receives a signal.
|
||||
|
||||
@section libdhcpPktFilter Switchable Packet Filter objects used by Interface Manager
|
||||
|
||||
The well known problem of DHCPv4 implementation is that it must be able to
|
||||
provision devices which don't have an IPv4 address yet (the IPv4 address is
|
||||
one of the configuration parameters provided by DHCP server to a client).
|
||||
One way to communicate with such a device is to send server's response to
|
||||
a broadcast address. An obvious drawback of this approach is that the server's
|
||||
response will be received and processed by all clients in the particular
|
||||
network. Therefore, the preferred approach is that the server unicasts its
|
||||
response to a new address being assigned for the client. This client will
|
||||
identify itself as a target of this message by checking chaddr and/or
|
||||
Client Identifier value. At the same time, the other clients in the network
|
||||
will not receive the unicast message. The major problem that arises with this
|
||||
approach is that the client without an IP address doesn't respond to ARP
|
||||
messages. As a result, server's response will not be sent over IP/UDP
|
||||
socket because the system kernel will fail to resolve client's link-layer
|
||||
address.
|
||||
|
||||
Kea supports the use of raw sockets to create a complete Data-link/IP/UDP/DHCPv4
|
||||
stack. By creating each layer of the outgoing packet, the Kea logic has full
|
||||
control over the frame contents and it may bypass the use of ARP to inject the
|
||||
link layer address into the frame. The raw socket is bound to a specific interface,
|
||||
not to the IP address/UDP port. Therefore, the system kernel doesn't have
|
||||
means to verify that Kea is listening to the DHCP traffic on the specific address
|
||||
and port. This has two major implications:
|
||||
- It is possible to run another DHCPv4 sever instance which will bind socket to the
|
||||
same address and port.
|
||||
- An attempt to send a unicast message to the DHCPv4 server will result in ICMP
|
||||
"Port Unreachable" message being sent by the kernel (which is unaware that the
|
||||
DHCPv4 service is actually running).
|
||||
In order to overcome these issues, the isc::dhcp::PktFilterLPF opens a
|
||||
regular IP/UDP socket which coexists with the raw socket. The socket is referred
|
||||
to as "fallback socket" in the Kea code. All packets received through this socket
|
||||
are discarded.
|
||||
|
||||
In general, the use of datagram sockets is preferred over raw sockets.
|
||||
For convenience, the switchable Packet Filter objects are used to manage
|
||||
sockets for different purposes. These objects implement the socket opening
|
||||
operation and sending/receiving messages over this socket. For example:
|
||||
the isc::dhcp::PktFilterLPF object opens a raw socket.
|
||||
The isc::dhcp::PktFilterLPF::send and isc::dhcp::PktFilterLPF::receive
|
||||
methods encode/decode full data-link/IP/UDP/DHCPv4 stack. The
|
||||
isc::dhcp::PktFilterInet supports sending and receiving messages over
|
||||
the regular IP/UDP socket. The isc::dhcp::PktFilterInet should be used in all
|
||||
cases when an application using the libdhcp++ doesn't require sending
|
||||
DHCP messages to a device which doesn't have an address yet.
|
||||
|
||||
|
||||
*/
|
||||
|
66
src/lib/dhcp/pkt_filter.cc
Normal file
66
src/lib/dhcp/pkt_filter.cc
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright (C) 2013 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 <config.h>
|
||||
#include <dhcp/iface_mgr.h>
|
||||
#include <dhcp/pkt_filter.h>
|
||||
|
||||
#include <sys/fcntl.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
int
|
||||
PktFilter::openFallbackSocket(const isc::asiolink::IOAddress& addr,
|
||||
const uint16_t port) {
|
||||
// Create socket.
|
||||
int sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (sock < 0) {
|
||||
isc_throw(SocketConfigError, "failed to create fallback socket for"
|
||||
" address " << addr.toText() << ", port " << port
|
||||
<< ", reason: " << strerror(errno));
|
||||
}
|
||||
// Bind the socket to a specified address and port.
|
||||
struct sockaddr_in addr4;
|
||||
memset(&addr4, 0, sizeof(addr4));
|
||||
addr4.sin_family = AF_INET;
|
||||
addr4.sin_addr.s_addr = htonl(addr);
|
||||
addr4.sin_port = htons(port);
|
||||
|
||||
if (bind(sock, reinterpret_cast<struct sockaddr*>(&addr4),
|
||||
sizeof(addr4)) < 0) {
|
||||
// Remember to close the socket if we failed to bind it.
|
||||
close(sock);
|
||||
isc_throw(SocketConfigError, "failed to bind fallback socket to"
|
||||
" address " << addr.toText() << ", port " << port
|
||||
<< ", reason: " << strerror(errno)
|
||||
<< " - is another DHCP server running?");
|
||||
}
|
||||
|
||||
// Set socket to non-blocking mode. This is to prevent the read from the
|
||||
// fallback socket to block message processing on the primary socket.
|
||||
if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) {
|
||||
close(sock);
|
||||
isc_throw(SocketConfigError, "failed to set SO_NONBLOCK option on the"
|
||||
" fallback socket, bound to " << addr.toText() << ", port "
|
||||
<< port << ", reason: " << strerror(errno));
|
||||
}
|
||||
// Successfully created and bound a fallback socket. Return a descriptor.
|
||||
return (sock);
|
||||
}
|
||||
|
||||
|
||||
} // end of isc::dhcp namespace
|
||||
} // end of isc namespace
|
@@ -67,20 +67,30 @@ public:
|
||||
/// @return true of the direct response is supported.
|
||||
virtual bool isDirectResponseSupported() const = 0;
|
||||
|
||||
/// @brief Open socket.
|
||||
/// @brief Open primary and fallback socket.
|
||||
///
|
||||
/// @param iface interface descriptor
|
||||
/// @param addr address on the interface to be used to send packets.
|
||||
/// @param port port number.
|
||||
/// @param receive_bcast configure socket to receive broadcast messages
|
||||
/// A method implementation in the derived class may open one or two
|
||||
/// sockets:
|
||||
/// - a primary socket - used for communication with clients. DHCP messages
|
||||
/// received using this socket are processed and the same socket is used
|
||||
/// to send a response to the client.
|
||||
/// - a fallback socket which is optionally opened if there is a need for
|
||||
/// the presence of the socket which can be bound to a specific IP address
|
||||
/// and UDP port (e.g. raw primary socket can't be). For the details, see
|
||||
/// the documentation of @c isc::dhcp::SocketInfo.
|
||||
///
|
||||
/// @param iface Interface descriptor.
|
||||
/// @param addr Address on the interface to be used to send packets.
|
||||
/// @param port Port number.
|
||||
/// @param receive_bcast Configure socket to receive broadcast messages
|
||||
/// @param send_bcast configure socket to send broadcast messages.
|
||||
///
|
||||
/// @return created socket's descriptor
|
||||
virtual int openSocket(const Iface& iface,
|
||||
const isc::asiolink::IOAddress& addr,
|
||||
const uint16_t port,
|
||||
const bool receive_bcast,
|
||||
const bool send_bcast) = 0;
|
||||
/// @return A structure describing a primary and fallback socket.
|
||||
virtual SocketInfo openSocket(const Iface& iface,
|
||||
const isc::asiolink::IOAddress& addr,
|
||||
const uint16_t port,
|
||||
const bool receive_bcast,
|
||||
const bool send_bcast) = 0;
|
||||
|
||||
/// @brief Receive packet over specified socket.
|
||||
///
|
||||
@@ -100,6 +110,32 @@ public:
|
||||
/// @return result of sending the packet. It is 0 if successful.
|
||||
virtual int send(const Iface& iface, uint16_t sockfd,
|
||||
const Pkt4Ptr& pkt) = 0;
|
||||
|
||||
protected:
|
||||
|
||||
/// @brief Default implementation to open a fallback socket.
|
||||
///
|
||||
/// This method provides a means to open a fallback socket and bind it
|
||||
/// to a given IPv4 address and UDP port. This function may be used by the
|
||||
/// derived classes to create a fallback socket. It can be overriden
|
||||
/// in the derived classes if it happens to be insufficient on some
|
||||
/// environments.
|
||||
///
|
||||
/// The fallback socket is meant to be opened together with the other socket
|
||||
/// (a.k.a. primary socket) used to receive and handle DHCPv4 traffic. The
|
||||
/// traffic received through the fallback should be dropped. The reasoning
|
||||
/// behind opening the fallback socket is explained in the documentation of
|
||||
/// @c isc::dhcp::SocketInfo structure.
|
||||
///
|
||||
/// @param addr An IPv4 address to bind the socket to.
|
||||
/// @param port A port number to bind socket to.
|
||||
///
|
||||
/// @return A fallback socket descriptor. This descriptor should be assigned
|
||||
/// to the @c fallbackfd_ field of the @c isc::dhcp::SocketInfo structure.
|
||||
/// @throw isc::dhcp::SocketConfigError if socket opening, binding or
|
||||
/// configuration fails.
|
||||
virtual int openFallbackSocket(const isc::asiolink::IOAddress& addr,
|
||||
const uint16_t port);
|
||||
};
|
||||
|
||||
/// Pointer to a PktFilter object.
|
||||
|
@@ -28,11 +28,12 @@ PktFilterInet::PktFilterInet()
|
||||
{
|
||||
}
|
||||
|
||||
int PktFilterInet::openSocket(const Iface& iface,
|
||||
const isc::asiolink::IOAddress& addr,
|
||||
const uint16_t port,
|
||||
const bool receive_bcast,
|
||||
const bool send_bcast) {
|
||||
SocketInfo
|
||||
PktFilterInet::openSocket(const Iface& iface,
|
||||
const isc::asiolink::IOAddress& addr,
|
||||
const uint16_t port,
|
||||
const bool receive_bcast,
|
||||
const bool send_bcast) {
|
||||
|
||||
struct sockaddr_in addr4;
|
||||
memset(&addr4, 0, sizeof(sockaddr));
|
||||
@@ -90,7 +91,8 @@ int PktFilterInet::openSocket(const Iface& iface,
|
||||
}
|
||||
#endif
|
||||
|
||||
return (sock);
|
||||
SocketInfo sock_desc(addr, port, sock);
|
||||
return (sock_desc);
|
||||
|
||||
}
|
||||
|
||||
|
@@ -44,20 +44,22 @@ public:
|
||||
return (false);
|
||||
}
|
||||
|
||||
/// @brief Open socket.
|
||||
/// @brief Open primary and fallback socket.
|
||||
///
|
||||
/// @param iface interface descriptor
|
||||
/// @param addr address on the interface to be used to send packets.
|
||||
/// @param port port number.
|
||||
/// @param receive_bcast configure socket to receive broadcast messages
|
||||
/// @param send_bcast configure socket to send broadcast messages.
|
||||
/// @param iface Interface descriptor.
|
||||
/// @param addr Address on the interface to be used to send packets.
|
||||
/// @param port Port number.
|
||||
/// @param receive_bcast Configure socket to receive broadcast messages
|
||||
/// @param send_bcast Configure socket to send broadcast messages.
|
||||
///
|
||||
/// @return created socket's descriptor
|
||||
virtual int openSocket(const Iface& iface,
|
||||
const isc::asiolink::IOAddress& addr,
|
||||
const uint16_t port,
|
||||
const bool receive_bcast,
|
||||
const bool send_bcast);
|
||||
/// @return A structure describing a primary and fallback socket.
|
||||
/// @throw isc::dhcp::SocketConfigError if error occurs when opening,
|
||||
/// binding or configuring the socket.
|
||||
virtual SocketInfo openSocket(const Iface& iface,
|
||||
const isc::asiolink::IOAddress& addr,
|
||||
const uint16_t port,
|
||||
const bool receive_bcast,
|
||||
const bool send_bcast);
|
||||
|
||||
/// @brief Receive packet over specified socket.
|
||||
///
|
||||
@@ -65,6 +67,10 @@ public:
|
||||
/// @param socket_info structure holding socket information
|
||||
///
|
||||
/// @return Received packet
|
||||
/// @throw isc::dhcp::SocketReadError if an error occurs during reception
|
||||
/// of the packet.
|
||||
/// @throw An execption thrown by the isc::dhcp::Pkt4 object if DHCPv4
|
||||
/// message parsing fails.
|
||||
virtual Pkt4Ptr receive(const Iface& iface, const SocketInfo& socket_info);
|
||||
|
||||
/// @brief Send packet over specified socket.
|
||||
@@ -74,6 +80,8 @@ public:
|
||||
/// @param pkt packet to be sent
|
||||
///
|
||||
/// @return result of sending a packet. It is 0 if successful.
|
||||
/// @throw isc::dhcp::SocketWriteError if an error occures during sending
|
||||
/// a DHCP message through the socket.
|
||||
virtual int send(const Iface& iface, uint16_t sockfd,
|
||||
const Pkt4Ptr& pkt);
|
||||
|
||||
|
@@ -102,13 +102,22 @@ using namespace isc::util;
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
|
||||
int
|
||||
PktFilterLPF::openSocket(const Iface& iface, const isc::asiolink::IOAddress&,
|
||||
SocketInfo
|
||||
PktFilterLPF::openSocket(const Iface& iface,
|
||||
const isc::asiolink::IOAddress& addr,
|
||||
const uint16_t port, const bool,
|
||||
const bool) {
|
||||
|
||||
// Open fallback socket first. If it fails, it will give us an indication
|
||||
// that there is another service (perhaps DHCP server) running.
|
||||
// The function will throw an exception and effectivelly cease opening
|
||||
// raw socket below.
|
||||
int fallback = openFallbackSocket(addr, port);
|
||||
|
||||
// The fallback is open, so we are good to open primary socket.
|
||||
int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
|
||||
if (sock < 0) {
|
||||
close(fallback);
|
||||
isc_throw(SocketConfigError, "Failed to create raw LPF socket");
|
||||
}
|
||||
|
||||
@@ -126,6 +135,7 @@ PktFilterLPF::openSocket(const Iface& iface, const isc::asiolink::IOAddress&,
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter_program,
|
||||
sizeof(filter_program)) < 0) {
|
||||
close(sock);
|
||||
close(fallback);
|
||||
isc_throw(SocketConfigError, "Failed to install packet filtering program"
|
||||
<< " on the socket " << sock);
|
||||
}
|
||||
@@ -142,17 +152,39 @@ PktFilterLPF::openSocket(const Iface& iface, const isc::asiolink::IOAddress&,
|
||||
if (bind(sock, reinterpret_cast<const struct sockaddr*>(&sa),
|
||||
sizeof(sa)) < 0) {
|
||||
close(sock);
|
||||
close(fallback);
|
||||
isc_throw(SocketConfigError, "Failed to bind LPF socket '" << sock
|
||||
<< "' to interface '" << iface.getName() << "'");
|
||||
}
|
||||
|
||||
return (sock);
|
||||
return (SocketInfo(addr, port, sock, fallback));
|
||||
|
||||
}
|
||||
|
||||
Pkt4Ptr
|
||||
PktFilterLPF::receive(const Iface& iface, const SocketInfo& socket_info) {
|
||||
uint8_t raw_buf[IfaceMgr::RCVBUFSIZE];
|
||||
// First let's get some data from the fallback socket. The data will be
|
||||
// discarded but we don't want the socket buffer to bloat. We get the
|
||||
// packets from the socket in loop but most of the time the loop will
|
||||
// end after receiving one packet. The call to recv returns immediately
|
||||
// when there is no data left on the socket because the socket is
|
||||
// non-blocking.
|
||||
// @todo In the normal conditions, both the primary socket and the fallback
|
||||
// socket are in sync as they are set to receive packets on the same
|
||||
// address and port. The reception of packets on the fallback socket
|
||||
// shouldn't cause significant lags in packet reception. If we find in the
|
||||
// future that it does, the sort of threshold could be set for the maximum
|
||||
// bytes received on the fallback socket in a single round. Further
|
||||
// optimizations would include an asynchronous read from the fallback socket
|
||||
// when the DHCP server is idle.
|
||||
int datalen;
|
||||
do {
|
||||
datalen = recv(socket_info.fallbackfd_, raw_buf, sizeof(raw_buf), 0);
|
||||
} while (datalen > 0);
|
||||
|
||||
// Now that we finished getting data from the fallback socket, we
|
||||
// have to get the data from the raw socket too.
|
||||
int data_len = read(socket_info.sockfd_, raw_buf, sizeof(raw_buf));
|
||||
// If negative value is returned by read(), it indicates that an
|
||||
// error occured. If returned value is 0, no data was read from the
|
||||
@@ -209,9 +241,16 @@ PktFilterLPF::send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt) {
|
||||
|
||||
OutputBuffer buf(14);
|
||||
|
||||
HWAddrPtr hwaddr(new HWAddr(iface.getMac(), iface.getMacLen(),
|
||||
iface.getHWType()));
|
||||
pkt->setLocalHWAddr(hwaddr);
|
||||
// Some interfaces may have no HW address - e.g. loopback interface.
|
||||
// For these interfaces the HW address length is 0. If this is the case,
|
||||
// then we will rely on the functions which construct the IP/UDP headers
|
||||
// to provide a default HW addres. Otherwise, create the HW address
|
||||
// object using the HW address of the interface.
|
||||
if (iface.getMacLen() > 0) {
|
||||
HWAddrPtr hwaddr(new HWAddr(iface.getMac(), iface.getMacLen(),
|
||||
iface.getHWType()));
|
||||
pkt->setLocalHWAddr(hwaddr);
|
||||
}
|
||||
|
||||
|
||||
// Ethernet frame header.
|
||||
|
@@ -41,21 +41,20 @@ public:
|
||||
return (true);
|
||||
}
|
||||
|
||||
/// @brief Open socket.
|
||||
/// @brief Open primary and fallback socket.
|
||||
///
|
||||
/// @param iface interface descriptor
|
||||
/// @param addr address on the interface to be used to send packets.
|
||||
/// @param port port number.
|
||||
/// @param receive_bcast configure socket to receive broadcast messages
|
||||
/// @param send_bcast configure socket to send broadcast messages.
|
||||
/// @param iface Interface descriptor.
|
||||
/// @param addr Address on the interface to be used to send packets.
|
||||
/// @param port Port number.
|
||||
/// @param receive_bcast Configure socket to receive broadcast messages
|
||||
/// @param send_bcast Configure socket to send broadcast messages.
|
||||
///
|
||||
/// @throw isc::NotImplemented always
|
||||
/// @return created socket's descriptor
|
||||
virtual int openSocket(const Iface& iface,
|
||||
const isc::asiolink::IOAddress& addr,
|
||||
const uint16_t port,
|
||||
const bool receive_bcast,
|
||||
const bool send_bcast);
|
||||
/// @return A structure describing a primary and fallback socket.
|
||||
virtual SocketInfo openSocket(const Iface& iface,
|
||||
const isc::asiolink::IOAddress& addr,
|
||||
const uint16_t port,
|
||||
const bool receive_bcast,
|
||||
const bool send_bcast);
|
||||
|
||||
/// @brief Receive packet over specified socket.
|
||||
///
|
||||
|
@@ -48,7 +48,9 @@ libdhcp___unittests_SOURCES += option_string_unittest.cc
|
||||
libdhcp___unittests_SOURCES += option_vendor_unittest.cc
|
||||
libdhcp___unittests_SOURCES += pkt4_unittest.cc
|
||||
libdhcp___unittests_SOURCES += pkt6_unittest.cc
|
||||
libdhcp___unittests_SOURCES += pkt_filter_unittest.cc
|
||||
libdhcp___unittests_SOURCES += pkt_filter_inet_unittest.cc
|
||||
libdhcp___unittests_SOURCES += pkt_filter_test_utils.h pkt_filter_test_utils.cc
|
||||
|
||||
if OS_LINUX
|
||||
libdhcp___unittests_SOURCES += pkt_filter_lpf_unittest.cc
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#include <dhcp/pkt6.h>
|
||||
#include <dhcp/pkt_filter.h>
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -78,19 +79,36 @@ public:
|
||||
return (false);
|
||||
}
|
||||
|
||||
/// Pretends to open socket. Only records a call to this function.
|
||||
/// This function returns fake socket descriptor (always the same).
|
||||
/// Note that the returned value has been selected to be unique
|
||||
/// (because real values are rather less than 255). Values greater
|
||||
/// than 255 are not recommended because they cause warnings to be
|
||||
/// reported by Valgrind when invoking close() on them.
|
||||
virtual int openSocket(const Iface&,
|
||||
const isc::asiolink::IOAddress&,
|
||||
const uint16_t,
|
||||
const bool,
|
||||
const bool) {
|
||||
/// @brief Pretend to open a socket.
|
||||
///
|
||||
/// This function doesn't open a real socket. It always returns the
|
||||
/// same fake socket descriptor. It also records the fact that it has
|
||||
/// been called in the public open_socket_called_ member.
|
||||
/// As in the case of opening a real socket, this function will check
|
||||
/// if there is another fake socket "bound" to the same address and port.
|
||||
/// If there is, it will throw an exception. This allows to simulate the
|
||||
/// conditions when one of the sockets can't be open because there is
|
||||
/// a socket already open and test how IfaceMgr will handle it.
|
||||
///
|
||||
/// @param iface An interface on which the socket is to be opened.
|
||||
/// @param addr An address to which the socket is to be bound.
|
||||
/// @param port A port to which the socket is to be bound.
|
||||
virtual SocketInfo openSocket(const Iface& iface,
|
||||
const isc::asiolink::IOAddress& addr,
|
||||
const uint16_t port,
|
||||
const bool,
|
||||
const bool) {
|
||||
// Check if there is any other socket bound to the specified address
|
||||
// and port on this interface.
|
||||
const Iface::SocketCollection& sockets = iface.getSockets();
|
||||
for (Iface::SocketCollection::const_iterator socket = sockets.begin();
|
||||
socket != sockets.end(); ++socket) {
|
||||
if ((socket->addr_ == addr) && (socket->port_ == port)) {
|
||||
isc_throw(SocketConfigError, "test socket bind error");
|
||||
}
|
||||
}
|
||||
open_socket_called_ = true;
|
||||
return (255);
|
||||
return (SocketInfo(addr, port, 255));
|
||||
}
|
||||
|
||||
/// Does nothing
|
||||
@@ -112,16 +130,97 @@ public:
|
||||
class NakedIfaceMgr: public IfaceMgr {
|
||||
// "Naked" Interface Manager, exposes internal fields
|
||||
public:
|
||||
|
||||
/// @brief Constructor.
|
||||
NakedIfaceMgr() {
|
||||
}
|
||||
IfaceCollection & getIfacesLst() { return ifaces_; }
|
||||
|
||||
/// @brief Returns the collection of existing interfaces.
|
||||
IfaceCollection& getIfacesLst() { return (ifaces_); }
|
||||
|
||||
/// @brief This function creates fictious interfaces with fictious
|
||||
/// addresses.
|
||||
///
|
||||
/// These interfaces can be used in tests that don't actually try
|
||||
/// to open the sockets on these interfaces. Some tests use mock
|
||||
/// objects to mimic sockets being open. These interfaces are
|
||||
/// suitable for such tests.
|
||||
void createIfaces() {
|
||||
|
||||
ifaces_.clear();
|
||||
|
||||
// local loopback
|
||||
ifaces_.push_back(createIface("lo", 0, "127.0.0.1"));
|
||||
// eth0
|
||||
ifaces_.push_back(createIface("eth0", 1, "10.0.0.1"));
|
||||
// eth1
|
||||
ifaces_.push_back(createIface("eth1", 2, "192.0.2.3"));
|
||||
}
|
||||
|
||||
/// @brief Create an object representing interface.
|
||||
///
|
||||
/// Apart from creating an interface, this function also sets the
|
||||
/// interface flags:
|
||||
/// - loopback flag if interface name is "lo"
|
||||
/// - up always true
|
||||
/// - running always true
|
||||
/// - inactive always to false
|
||||
///
|
||||
/// If one needs to modify the default flag settings, the setIfaceFlags
|
||||
/// function should be used.
|
||||
///
|
||||
/// @param name A name of the interface to be created.
|
||||
/// @param ifindex An index of the interface to be created.
|
||||
/// @param addr An IP address to be assigned to the interface.
|
||||
///
|
||||
/// @return An object representing interface.
|
||||
static Iface createIface(const std::string& name, const int ifindex,
|
||||
const std::string& addr) {
|
||||
Iface iface(name, ifindex);
|
||||
iface.addAddress(IOAddress(addr));
|
||||
if (name == "lo") {
|
||||
iface.flag_loopback_ = true;
|
||||
}
|
||||
iface.flag_up_ = true;
|
||||
iface.flag_running_ = true;
|
||||
iface.inactive4_ = false;
|
||||
return (iface);
|
||||
}
|
||||
|
||||
/// @brief Modified flags on the interface.
|
||||
///
|
||||
/// @param name A name of the interface.
|
||||
/// @param loopback A new value of the loopback flag.
|
||||
/// @param up A new value of the up flag.
|
||||
/// @param running A new value of the running flag.
|
||||
/// @param inactive A new value of the inactive flag.
|
||||
void setIfaceFlags(const std::string& name, const bool loopback,
|
||||
const bool up, const bool running,
|
||||
const bool inactive) {
|
||||
for (IfaceMgr::IfaceCollection::iterator iface = ifaces_.begin();
|
||||
iface != ifaces_.end(); ++iface) {
|
||||
if (iface->getName() == name) {
|
||||
iface->flag_loopback_ = loopback;
|
||||
iface->flag_up_ = up;
|
||||
iface->flag_running_ = running;
|
||||
iface->inactive4_ = inactive;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Dummy class for now, but this will be expanded when needed
|
||||
/// @brief A test fixture class for IfaceMgr.
|
||||
///
|
||||
/// @todo Sockets being opened by IfaceMgr tests should be managed by
|
||||
/// the test fixture. In particular, the class should close sockets after
|
||||
/// each test. Current approach where test cases are responsible for
|
||||
/// closing sockets is resource leak prone, especially in case of the
|
||||
/// test failure path.
|
||||
class IfaceMgrTest : public ::testing::Test {
|
||||
public:
|
||||
// These are empty for now, but let's keep them around
|
||||
IfaceMgrTest() {
|
||||
/// @brief Constructor.
|
||||
IfaceMgrTest()
|
||||
: errors_count_(0) {
|
||||
}
|
||||
|
||||
~IfaceMgrTest() {
|
||||
@@ -166,6 +265,26 @@ public:
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/// @brief Implements an IfaceMgr error handler.
|
||||
///
|
||||
/// This function can be installed as an error handler for the
|
||||
/// IfaceMgr::openSockets4 function. The error handler is invoked
|
||||
/// when an attempt to open a particular socket fails for any reason.
|
||||
/// Typically, the error handler will log a warning. When the error
|
||||
/// handler returns, the openSockets4 function should continue opening
|
||||
/// sockets on other interfaces.
|
||||
///
|
||||
/// @param errmsg An error string indicating the reason for failure.
|
||||
void ifaceMgrErrorHandler(const std::string&) {
|
||||
// Increase the counter of invocations to this function. By checking
|
||||
// this number, a test amy check if the expected number of errors
|
||||
// has occurred.
|
||||
++errors_count_;
|
||||
}
|
||||
|
||||
/// Holds the invocation counter for ifaceMgrErrorHandler.
|
||||
int errors_count_;
|
||||
|
||||
};
|
||||
|
||||
// We need some known interface to work reliably. Loopback interface is named
|
||||
@@ -986,6 +1105,54 @@ TEST_F(IfaceMgrTest, setMatchingPacketFilter) {
|
||||
EXPECT_TRUE(iface_mgr->isDirectResponseSupported());
|
||||
}
|
||||
|
||||
// This test checks that it is not possible to open two sockets: IP/UDP
|
||||
// and raw (LPF) socket and bind to the same address and port. The
|
||||
// raw socket should be opened together with the fallback IP/UDP socket.
|
||||
// The fallback socket should fail to open when there is another IP/UDP
|
||||
// socket bound to the same address and port. Failing to open the fallback
|
||||
// socket should preclude the raw socket from being open.
|
||||
TEST_F(IfaceMgrTest, checkPacketFilterLPFSocket) {
|
||||
IOAddress loAddr("127.0.0.1");
|
||||
int socket1 = -1, socket2 = -1;
|
||||
// Create two instances of IfaceMgr.
|
||||
boost::scoped_ptr<NakedIfaceMgr> iface_mgr1(new NakedIfaceMgr());
|
||||
ASSERT_TRUE(iface_mgr1);
|
||||
boost::scoped_ptr<NakedIfaceMgr> iface_mgr2(new NakedIfaceMgr());
|
||||
ASSERT_TRUE(iface_mgr2);
|
||||
|
||||
// Let IfaceMgr figure out which Packet Filter to use when
|
||||
// direct response capability is not desired. It should pick
|
||||
// PktFilterInet.
|
||||
EXPECT_NO_THROW(iface_mgr1->setMatchingPacketFilter(false));
|
||||
// Let's open a loopback socket with handy unpriviliged port number
|
||||
socket1 = iface_mgr1->openSocket(LOOPBACK, loAddr,
|
||||
DHCP4_SERVER_PORT + 10000);
|
||||
|
||||
EXPECT_GE(socket1, 0);
|
||||
|
||||
// Then the second use PkFilterLPF mode
|
||||
EXPECT_NO_THROW(iface_mgr2->setMatchingPacketFilter(true));
|
||||
|
||||
// The socket is open and bound. Another attempt to open socket and
|
||||
// bind to the same address and port should result in an exception.
|
||||
EXPECT_THROW(
|
||||
socket2 = iface_mgr2->openSocket(LOOPBACK, loAddr,
|
||||
DHCP4_SERVER_PORT + 10000),
|
||||
isc::dhcp::SocketConfigError
|
||||
);
|
||||
// Surprisingly we managed to open another socket. We have to close it
|
||||
// to prevent resource leak.
|
||||
if (socket2 >= 0) {
|
||||
close(socket2);
|
||||
ADD_FAILURE() << "Two sockets opened and bound to the same IP"
|
||||
" address and UDP port";
|
||||
}
|
||||
|
||||
if (socket1 >= 0) {
|
||||
close(socket1);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// This non-Linux specific test checks whether it is possible to use
|
||||
@@ -1045,6 +1212,157 @@ TEST_F(IfaceMgrTest, socket4) {
|
||||
close(socket1);
|
||||
}
|
||||
|
||||
// This test verifies that IPv4 sockets are open on all interfaces (except
|
||||
// loopback), when interfaces are up, running and active (not disabled from
|
||||
// the DHCP configuration).
|
||||
TEST_F(IfaceMgrTest, openSockets4) {
|
||||
NakedIfaceMgr ifacemgr;
|
||||
|
||||
// Remove all real interfaces and create a set of dummy interfaces.
|
||||
ifacemgr.createIfaces();
|
||||
|
||||
// Use the custom packet filter object. This object mimics the socket
|
||||
// opening operation - the real socket is not open.
|
||||
boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
|
||||
ASSERT_TRUE(custom_packet_filter);
|
||||
ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
|
||||
|
||||
// Simulate opening sockets using the dummy packet filter.
|
||||
ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL));
|
||||
|
||||
// Expect that the sockets are open on both eth0 and eth1.
|
||||
EXPECT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size());
|
||||
EXPECT_EQ(1, ifacemgr.getIface("eth1")->getSockets().size());
|
||||
// Socket shouldn't have been opened on loopback.
|
||||
EXPECT_TRUE(ifacemgr.getIface("lo")->getSockets().empty());
|
||||
}
|
||||
|
||||
// This test verifies that the socket is not open on the interface which is
|
||||
// down, but sockets are open on all other non-loopback interfaces.
|
||||
TEST_F(IfaceMgrTest, openSockets4IfaceDown) {
|
||||
NakedIfaceMgr ifacemgr;
|
||||
|
||||
// Remove all real interfaces and create a set of dummy interfaces.
|
||||
ifacemgr.createIfaces();
|
||||
|
||||
boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
|
||||
ASSERT_TRUE(custom_packet_filter);
|
||||
ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
|
||||
|
||||
// Boolean parameters specify that eth0 is:
|
||||
// - not a loopback
|
||||
// - is "down" (not up)
|
||||
// - is not running
|
||||
// - is active (is not inactive)
|
||||
ifacemgr.setIfaceFlags("eth0", false, false, true, false);
|
||||
ASSERT_FALSE(ifacemgr.getIface("eth0")->flag_up_);
|
||||
ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL));
|
||||
|
||||
// There should be no socket on eth0 open, because interface was down.
|
||||
EXPECT_TRUE(ifacemgr.getIface("eth0")->getSockets().empty());
|
||||
// Expecting that the socket is open on eth1 because it was up, running
|
||||
// and active.
|
||||
EXPECT_EQ(1, ifacemgr.getIface("eth1")->getSockets().size());
|
||||
// Never open socket on loopback interface.
|
||||
EXPECT_TRUE(ifacemgr.getIface("lo")->getSockets().empty());
|
||||
}
|
||||
|
||||
// This test verifies that the socket is not open on the interface which is
|
||||
// disabled from the DHCP configuration, but sockets are open on all other
|
||||
// non-loopback interfaces.
|
||||
TEST_F(IfaceMgrTest, openSockets4IfaceInactive) {
|
||||
NakedIfaceMgr ifacemgr;
|
||||
|
||||
// Remove all real interfaces and create a set of dummy interfaces.
|
||||
ifacemgr.createIfaces();
|
||||
|
||||
boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
|
||||
ASSERT_TRUE(custom_packet_filter);
|
||||
ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
|
||||
|
||||
// Boolean parameters specify that eth1 is:
|
||||
// - not a loopback
|
||||
// - is up
|
||||
// - is running
|
||||
// - is inactive
|
||||
ifacemgr.setIfaceFlags("eth1", false, true, true, true);
|
||||
ASSERT_TRUE(ifacemgr.getIface("eth1")->inactive4_);
|
||||
ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL));
|
||||
|
||||
// The socket on eth0 should be open because interface is up, running and
|
||||
// active (not disabled through DHCP configuration, for example).
|
||||
EXPECT_EQ(1, ifacemgr.getIface("eth0")->getSockets().size());
|
||||
// There should be no socket open on eth1 because it was marked inactive.
|
||||
EXPECT_TRUE(ifacemgr.getIface("eth1")->getSockets().empty());
|
||||
// Sockets are not open on loopback interfaces too.
|
||||
EXPECT_TRUE(ifacemgr.getIface("lo")->getSockets().empty());
|
||||
}
|
||||
|
||||
// Test that exception is thrown when trying to bind a new socket to the port
|
||||
// and address which is already in use by another socket.
|
||||
TEST_F(IfaceMgrTest, openSockets4NoErrorHandler) {
|
||||
NakedIfaceMgr ifacemgr;
|
||||
|
||||
// Remove all real interfaces and create a set of dummy interfaces.
|
||||
ifacemgr.createIfaces();
|
||||
|
||||
boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
|
||||
ASSERT_TRUE(custom_packet_filter);
|
||||
ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
|
||||
|
||||
// Open socket on eth1. The openSockets4 should detect that this
|
||||
// socket has been already open and an attempt to open another socket
|
||||
// and bind to this address and port should fail.
|
||||
ASSERT_NO_THROW(ifacemgr.openSocket("eth1", IOAddress("192.0.2.3"),
|
||||
DHCP4_SERVER_PORT));
|
||||
|
||||
// The function throws an exception when it tries to open a socket
|
||||
// and bind it to the address in use.
|
||||
EXPECT_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, NULL),
|
||||
isc::dhcp::SocketConfigError);
|
||||
|
||||
}
|
||||
|
||||
// Test that the external error handler is called when trying to bind a new
|
||||
// socket to the address and port being in use. The sockets on the other
|
||||
// interfaces should open just fine..
|
||||
TEST_F(IfaceMgrTest, openSocket4ErrorHandler) {
|
||||
NakedIfaceMgr ifacemgr;
|
||||
|
||||
// Remove all real interfaces and create a set of dummy interfaces.
|
||||
ifacemgr.createIfaces();
|
||||
|
||||
boost::shared_ptr<TestPktFilter> custom_packet_filter(new TestPktFilter());
|
||||
ASSERT_TRUE(custom_packet_filter);
|
||||
ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
|
||||
|
||||
// Open socket on eth0. The openSockets4 should detect that this
|
||||
// socket has been already open and an attempt to open another socket
|
||||
// and bind to this address and port should fail.
|
||||
ASSERT_NO_THROW(ifacemgr.openSocket("eth0", IOAddress("10.0.0.1"),
|
||||
DHCP4_SERVER_PORT));
|
||||
|
||||
// Install an error handler before trying to open sockets. This handler
|
||||
// should be called when the IfaceMgr fails to open socket on eth0.
|
||||
isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
|
||||
boost::bind(&IfaceMgrTest::ifaceMgrErrorHandler, this, _1);
|
||||
ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, error_handler));
|
||||
// We expect that an error occured when we tried to open a socket on
|
||||
// eth0, but the socket on eth1 should open just fine.
|
||||
EXPECT_EQ(1, errors_count_);
|
||||
|
||||
// Reset errors count.
|
||||
errors_count_ = 0;
|
||||
|
||||
// Now that we have two sockets open, we can try this again but this time
|
||||
// we should get two errors: one when opening a socket on eth0, another one
|
||||
// when opening a socket on eth1.
|
||||
ASSERT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, error_handler));
|
||||
EXPECT_EQ(2, errors_count_);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Test the Iface structure itself
|
||||
TEST_F(IfaceMgrTest, iface) {
|
||||
boost::scoped_ptr<Iface> iface;
|
||||
@@ -1118,18 +1436,28 @@ TEST_F(IfaceMgrTest, iface_methods) {
|
||||
TEST_F(IfaceMgrTest, socketInfo) {
|
||||
|
||||
// Check that socketinfo for IPv4 socket is functional
|
||||
SocketInfo sock1(7, IOAddress("192.0.2.56"), DHCP4_SERVER_PORT + 7);
|
||||
SocketInfo sock1(IOAddress("192.0.2.56"), DHCP4_SERVER_PORT + 7, 7);
|
||||
EXPECT_EQ(7, sock1.sockfd_);
|
||||
EXPECT_EQ(-1, sock1.fallbackfd_);
|
||||
EXPECT_EQ("192.0.2.56", sock1.addr_.toText());
|
||||
EXPECT_EQ(AF_INET, sock1.family_);
|
||||
EXPECT_EQ(DHCP4_SERVER_PORT + 7, sock1.port_);
|
||||
|
||||
// Check that non-default value of the fallback socket descriptor is set
|
||||
SocketInfo sock2(IOAddress("192.0.2.53"), DHCP4_SERVER_PORT + 8, 8, 10);
|
||||
EXPECT_EQ(8, sock2.sockfd_);
|
||||
EXPECT_EQ(10, sock2.fallbackfd_);
|
||||
EXPECT_EQ("192.0.2.53", sock2.addr_.toText());
|
||||
EXPECT_EQ(AF_INET, sock2.family_);
|
||||
EXPECT_EQ(DHCP4_SERVER_PORT + 8, sock2.port_);
|
||||
|
||||
// Check that socketinfo for IPv6 socket is functional
|
||||
SocketInfo sock2(9, IOAddress("2001:db8:1::56"), DHCP4_SERVER_PORT + 9);
|
||||
EXPECT_EQ(9, sock2.sockfd_);
|
||||
EXPECT_EQ("2001:db8:1::56", sock2.addr_.toText());
|
||||
EXPECT_EQ(AF_INET6, sock2.family_);
|
||||
EXPECT_EQ(DHCP4_SERVER_PORT + 9, sock2.port_);
|
||||
SocketInfo sock3(IOAddress("2001:db8:1::56"), DHCP4_SERVER_PORT + 9, 9);
|
||||
EXPECT_EQ(9, sock3.sockfd_);
|
||||
EXPECT_EQ(-1, sock3.fallbackfd_);
|
||||
EXPECT_EQ("2001:db8:1::56", sock3.addr_.toText());
|
||||
EXPECT_EQ(AF_INET6, sock3.family_);
|
||||
EXPECT_EQ(DHCP4_SERVER_PORT + 9, sock3.port_);
|
||||
|
||||
// Now let's test if IfaceMgr handles socket info properly
|
||||
scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
|
||||
@@ -1137,6 +1465,7 @@ TEST_F(IfaceMgrTest, socketInfo) {
|
||||
ASSERT_TRUE(loopback);
|
||||
loopback->addSocket(sock1);
|
||||
loopback->addSocket(sock2);
|
||||
loopback->addSocket(sock3);
|
||||
|
||||
Pkt6 pkt6(DHCPV6_REPLY, 123456);
|
||||
|
||||
@@ -1191,6 +1520,7 @@ TEST_F(IfaceMgrTest, socketInfo) {
|
||||
|
||||
EXPECT_NO_THROW(
|
||||
ifacemgr->getIface(LOOPBACK)->delSocket(7);
|
||||
ifacemgr->getIface(LOOPBACK)->delSocket(8);
|
||||
);
|
||||
|
||||
// It should throw again, there's no usable socket anymore.
|
||||
|
@@ -17,6 +17,7 @@
|
||||
#include <dhcp/iface_mgr.h>
|
||||
#include <dhcp/pkt4.h>
|
||||
#include <dhcp/pkt_filter_inet.h>
|
||||
#include <dhcp/tests/pkt_filter_test_utils.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -32,63 +33,12 @@ const uint16_t PORT = 10067;
|
||||
/// Size of the buffer holding received packets.
|
||||
const size_t RECV_BUF_SIZE = 2048;
|
||||
|
||||
/// This class handles has simple algorithm checking
|
||||
/// presence of loopback interface and initializing
|
||||
/// its index.
|
||||
class PktFilterInetTest : public ::testing::Test {
|
||||
// Test fixture class inherits from the class common for all packet
|
||||
// filter tests.
|
||||
class PktFilterInetTest : public isc::dhcp::test::PktFilterTest {
|
||||
public:
|
||||
|
||||
/// @brief Constructor
|
||||
///
|
||||
/// This constructor initializes socket_ member to a negative value.
|
||||
/// Explcit initialization is performed here because some of the
|
||||
/// tests do not initialize this value. In such cases, destructor
|
||||
/// could invoke close() on uninitialized socket descriptor which
|
||||
/// would result in errors being reported by Valgrind.
|
||||
PktFilterInetTest()
|
||||
: socket_(-1) {
|
||||
// Initialize ifname_ and ifindex_.
|
||||
loInit();
|
||||
PktFilterInetTest() : PktFilterTest(PORT) {
|
||||
}
|
||||
|
||||
/// @brief Destructor
|
||||
///
|
||||
/// Closes open socket (if any).
|
||||
~PktFilterInetTest() {
|
||||
// Cleanup after each test. This guarantees
|
||||
// that the socket does not hang after a test.
|
||||
if (socket_ >= 0) {
|
||||
close(socket_);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Detect loopback interface.
|
||||
///
|
||||
/// @todo this function will be removed once cross-OS interface
|
||||
/// detection is implemented
|
||||
void loInit() {
|
||||
if (if_nametoindex("lo") > 0) {
|
||||
ifname_ = "lo";
|
||||
ifindex_ = if_nametoindex("lo");
|
||||
|
||||
} else if (if_nametoindex("lo0") > 0) {
|
||||
ifname_ = "lo0";
|
||||
ifindex_ = if_nametoindex("lo0");
|
||||
|
||||
} else {
|
||||
std::cout << "Failed to detect loopback interface. Neither "
|
||||
<< "lo nor lo0 worked. Giving up." << std::endl;
|
||||
FAIL();
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
std::string ifname_; ///< Loopback interface name
|
||||
uint16_t ifindex_; ///< Loopback interface index.
|
||||
int socket_; ///< Socket descriptor.
|
||||
|
||||
};
|
||||
|
||||
// This test verifies that the PktFilterInet class reports its lack
|
||||
@@ -112,55 +62,19 @@ TEST_F(PktFilterInetTest, openSocket) {
|
||||
|
||||
// Try to open socket.
|
||||
PktFilterInet pkt_filter;
|
||||
socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
|
||||
// Check that socket has been opened.
|
||||
ASSERT_GE(socket_, 0);
|
||||
sock_info_ = pkt_filter.openSocket(iface, addr, PORT,
|
||||
false, false);
|
||||
// For the packet filter in use, the fallback socket shouldn't be opened.
|
||||
// Fallback is typically opened for raw sockets.
|
||||
EXPECT_LT(sock_info_.fallbackfd_, 0);
|
||||
|
||||
// Verify that the socket belongs to AF_INET family.
|
||||
sockaddr_in sock_address;
|
||||
socklen_t sock_address_len = sizeof(sock_address);
|
||||
ASSERT_EQ(0, getsockname(socket_, reinterpret_cast<sockaddr*>(&sock_address),
|
||||
&sock_address_len));
|
||||
EXPECT_EQ(AF_INET, sock_address.sin_family);
|
||||
|
||||
// Verify that the socket is bound the appropriate address.
|
||||
const std::string bind_addr(inet_ntoa(sock_address.sin_addr));
|
||||
EXPECT_EQ("127.0.0.1", bind_addr);
|
||||
|
||||
// Verify that the socket is bound to appropriate port.
|
||||
EXPECT_EQ(PORT, ntohs(sock_address.sin_port));
|
||||
|
||||
// Verify that the socket has SOCK_DGRAM type.
|
||||
int sock_type;
|
||||
socklen_t sock_type_len = sizeof(sock_type);
|
||||
ASSERT_EQ(0, getsockopt(socket_, SOL_SOCKET, SO_TYPE, &sock_type, &sock_type_len));
|
||||
EXPECT_EQ(SOCK_DGRAM, sock_type);
|
||||
// Test the primary socket.
|
||||
testDgramSocket(sock_info_.sockfd_);
|
||||
}
|
||||
|
||||
// This test verifies that the packet is correctly sent over the INET
|
||||
// datagram socket.
|
||||
TEST_F(PktFilterInetTest, send) {
|
||||
// Let's create a DHCPv4 packet.
|
||||
Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
|
||||
ASSERT_TRUE(pkt);
|
||||
|
||||
// Set required fields.
|
||||
pkt->setLocalAddr(IOAddress("127.0.0.1"));
|
||||
pkt->setRemoteAddr(IOAddress("127.0.0.1"));
|
||||
pkt->setRemotePort(PORT);
|
||||
pkt->setLocalPort(PORT + 1);
|
||||
pkt->setIndex(ifindex_);
|
||||
pkt->setIface(ifname_);
|
||||
pkt->setHops(6);
|
||||
pkt->setSecs(42);
|
||||
pkt->setCiaddr(IOAddress("192.0.2.1"));
|
||||
pkt->setSiaddr(IOAddress("192.0.2.2"));
|
||||
pkt->setYiaddr(IOAddress("192.0.2.3"));
|
||||
pkt->setGiaddr(IOAddress("192.0.2.4"));
|
||||
|
||||
// Create the on-wire data.
|
||||
ASSERT_NO_THROW(pkt->pack());
|
||||
|
||||
// Packet will be sent over loopback interface.
|
||||
Iface iface(ifname_, ifindex_);
|
||||
IOAddress addr("127.0.0.1");
|
||||
@@ -170,27 +84,27 @@ TEST_F(PktFilterInetTest, send) {
|
||||
// Open socket. We don't check that the socket has appropriate
|
||||
// options and family set because we have checked that in the
|
||||
// openSocket test already.
|
||||
socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
|
||||
ASSERT_GE(socket_, 0);
|
||||
sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
|
||||
ASSERT_GE(sock_info_.sockfd_, 0);
|
||||
|
||||
// Send the packet over the socket.
|
||||
ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
|
||||
ASSERT_NO_THROW(pkt_filter.send(iface, sock_info_.sockfd_, test_message_));
|
||||
|
||||
// Read the data from socket.
|
||||
fd_set readfds;
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(socket_, &readfds);
|
||||
|
||||
FD_SET(sock_info_.sockfd_, &readfds);
|
||||
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = 5;
|
||||
timeout.tv_usec = 0;
|
||||
int result = select(socket_ + 1, &readfds, NULL, NULL, &timeout);
|
||||
int result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout);
|
||||
// We should receive some data from loopback interface.
|
||||
ASSERT_GT(result, 0);
|
||||
|
||||
// Get the actual data.
|
||||
uint8_t rcv_buf[RECV_BUF_SIZE];
|
||||
result = recv(socket_, rcv_buf, RECV_BUF_SIZE, 0);
|
||||
result = recv(sock_info_.sockfd_, rcv_buf, RECV_BUF_SIZE, 0);
|
||||
ASSERT_GT(result, 0);
|
||||
|
||||
// Create the DHCPv4 packet from the received data.
|
||||
@@ -200,47 +114,16 @@ TEST_F(PktFilterInetTest, send) {
|
||||
// Parse the packet.
|
||||
ASSERT_NO_THROW(rcvd_pkt->unpack());
|
||||
|
||||
// Verify that the received packet matches sent packet.
|
||||
EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
|
||||
EXPECT_EQ(pkt->getOp(), rcvd_pkt->getOp());
|
||||
EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
|
||||
EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
|
||||
EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
|
||||
EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
|
||||
EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
|
||||
EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
|
||||
EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
|
||||
EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
|
||||
EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
|
||||
EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
|
||||
EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
|
||||
// Check if the received message is correct.
|
||||
testRcvdMessage(rcvd_pkt);
|
||||
|
||||
}
|
||||
|
||||
// This test verifies that the DHCPv4 packet is correctly received via
|
||||
// INET datagram socket and that it matches sent packet.
|
||||
TEST_F(PktFilterInetTest, receive) {
|
||||
// Let's create a DHCPv4 packet.
|
||||
Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
|
||||
ASSERT_TRUE(pkt);
|
||||
|
||||
// Set required fields.
|
||||
pkt->setLocalAddr(IOAddress("127.0.0.1"));
|
||||
pkt->setRemoteAddr(IOAddress("127.0.0.1"));
|
||||
pkt->setRemotePort(PORT);
|
||||
pkt->setLocalPort(PORT + 1);
|
||||
pkt->setIndex(ifindex_);
|
||||
pkt->setIface(ifname_);
|
||||
pkt->setHops(6);
|
||||
pkt->setSecs(42);
|
||||
pkt->setCiaddr(IOAddress("192.0.2.1"));
|
||||
pkt->setSiaddr(IOAddress("192.0.2.2"));
|
||||
pkt->setYiaddr(IOAddress("192.0.2.3"));
|
||||
pkt->setGiaddr(IOAddress("192.0.2.4"));
|
||||
|
||||
// Create the on-wire data.
|
||||
ASSERT_NO_THROW(pkt->pack());
|
||||
|
||||
// Packet will be sent over loopback interface.
|
||||
// Packet will be received over loopback interface.
|
||||
Iface iface(ifname_, ifindex_);
|
||||
IOAddress addr("127.0.0.1");
|
||||
|
||||
@@ -249,35 +132,22 @@ TEST_F(PktFilterInetTest, receive) {
|
||||
// Open socket. We don't check that the socket has appropriate
|
||||
// options and family set because we have checked that in the
|
||||
// openSocket test already.
|
||||
socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
|
||||
ASSERT_GE(socket_, 0);
|
||||
sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
|
||||
ASSERT_GE(sock_info_.sockfd_, 0);
|
||||
|
||||
// Send the packet over the socket.
|
||||
ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
|
||||
// Send a DHCPv4 message to the local loopback address and server's port.
|
||||
sendMessage();
|
||||
|
||||
// Receive the packet.
|
||||
SocketInfo socket_info(socket_, IOAddress("127.0.0.1"), PORT);
|
||||
Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, socket_info);
|
||||
Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, sock_info_);
|
||||
// Check that the packet has been correctly received.
|
||||
ASSERT_TRUE(rcvd_pkt);
|
||||
|
||||
// Parse the packet.
|
||||
ASSERT_NO_THROW(rcvd_pkt->unpack());
|
||||
|
||||
// Verify that the received packet matches sent packet.
|
||||
EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
|
||||
EXPECT_EQ(pkt->getOp(), rcvd_pkt->getOp());
|
||||
EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
|
||||
EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
|
||||
EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
|
||||
EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
|
||||
EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
|
||||
EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
|
||||
EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
|
||||
EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
|
||||
EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
|
||||
EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
|
||||
EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
|
||||
// Check if the received message is correct.
|
||||
testRcvdMessage(rcvd_pkt);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
@@ -18,6 +18,7 @@
|
||||
#include <dhcp/pkt4.h>
|
||||
#include <dhcp/pkt_filter_lpf.h>
|
||||
#include <dhcp/protocol_util.h>
|
||||
#include <dhcp/tests/pkt_filter_test_utils.h>
|
||||
#include <util/buffer.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
@@ -36,63 +37,12 @@ const uint16_t PORT = 10067;
|
||||
/// Size of the buffer holding received packets.
|
||||
const size_t RECV_BUF_SIZE = 2048;
|
||||
|
||||
/// This class handles has simple algorithm checking
|
||||
/// presence of loopback interface and initializing
|
||||
/// its index.
|
||||
class PktFilterLPFTest : public ::testing::Test {
|
||||
// Test fixture class inherits from the class common for all packet
|
||||
// filter tests.
|
||||
class PktFilterLPFTest : public isc::dhcp::test::PktFilterTest {
|
||||
public:
|
||||
|
||||
/// @brief Constructor
|
||||
///
|
||||
/// This constructor initializes socket_ member to a negative value.
|
||||
/// Explcit initialization is performed here because some of the
|
||||
/// tests do not initialize this value. In such cases, destructor
|
||||
/// could invoke close() on uninitialized socket descriptor which
|
||||
/// would result in errors being reported by Valgrind.
|
||||
PktFilterLPFTest()
|
||||
: socket_(-1) {
|
||||
// Initialize ifname_ and ifindex_.
|
||||
loInit();
|
||||
PktFilterLPFTest() : PktFilterTest(PORT) {
|
||||
}
|
||||
|
||||
/// @brief Destructor
|
||||
///
|
||||
/// Closes open socket (if any).
|
||||
~PktFilterLPFTest() {
|
||||
// Cleanup after each test. This guarantees
|
||||
// that the socket does not hang after a test.
|
||||
if (socket_ >= 0) {
|
||||
close(socket_);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Detect loopback interface.
|
||||
///
|
||||
/// @todo this function will be removed once cross-OS interface
|
||||
/// detection is implemented
|
||||
void loInit() {
|
||||
if (if_nametoindex("lo") > 0) {
|
||||
ifname_ = "lo";
|
||||
ifindex_ = if_nametoindex("lo");
|
||||
|
||||
} else if (if_nametoindex("lo0") > 0) {
|
||||
ifname_ = "lo0";
|
||||
ifindex_ = if_nametoindex("lo0");
|
||||
|
||||
} else {
|
||||
std::cout << "Failed to detect loopback interface. Neither "
|
||||
<< "lo nor lo0 worked. Giving up." << std::endl;
|
||||
FAIL();
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
std::string ifname_; ///< Loopback interface name
|
||||
uint16_t ifindex_; ///< Loopback interface index.
|
||||
int socket_; ///< Socket descriptor.
|
||||
|
||||
};
|
||||
|
||||
// This test verifies that the PktFilterLPF class reports its capability
|
||||
@@ -124,14 +74,18 @@ TEST_F(PktFilterLPFTest, DISABLED_openSocket) {
|
||||
|
||||
// Try to open socket.
|
||||
PktFilterLPF pkt_filter;
|
||||
socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
|
||||
// Check that socket has been opened.
|
||||
ASSERT_GE(socket_, 0);
|
||||
sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
|
||||
|
||||
// Check that the primary socket has been opened.
|
||||
ASSERT_GE(sock_info_.sockfd_, 0);
|
||||
// Check that the fallback socket has been opened too.
|
||||
ASSERT_GE(sock_info_.fallbackfd_, 0);
|
||||
|
||||
// Verify that the socket belongs to AF_PACKET family.
|
||||
sockaddr_ll sock_address;
|
||||
socklen_t sock_address_len = sizeof(sock_address);
|
||||
ASSERT_EQ(0, getsockname(socket_, reinterpret_cast<sockaddr*>(&sock_address),
|
||||
ASSERT_EQ(0, getsockname(sock_info_.sockfd_,
|
||||
reinterpret_cast<sockaddr*>(&sock_address),
|
||||
&sock_address_len));
|
||||
EXPECT_EQ(AF_PACKET, sock_address.sll_family);
|
||||
|
||||
@@ -141,39 +95,14 @@ TEST_F(PktFilterLPFTest, DISABLED_openSocket) {
|
||||
// Verify that the socket has SOCK_RAW type.
|
||||
int sock_type;
|
||||
socklen_t sock_type_len = sizeof(sock_type);
|
||||
ASSERT_EQ(0, getsockopt(socket_, SOL_SOCKET, SO_TYPE, &sock_type, &sock_type_len));
|
||||
ASSERT_EQ(0, getsockopt(sock_info_.sockfd_, SOL_SOCKET, SO_TYPE,
|
||||
&sock_type, &sock_type_len));
|
||||
EXPECT_EQ(SOCK_RAW, sock_type);
|
||||
}
|
||||
|
||||
// This test verifies correctness of sending DHCP packet through the raw
|
||||
// socket, whereby all IP stack headers are hand-crafted.
|
||||
TEST_F(PktFilterLPFTest, DISABLED_send) {
|
||||
// Let's create a DHCPv4 packet.
|
||||
Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
|
||||
ASSERT_TRUE(pkt);
|
||||
|
||||
// Set required fields.
|
||||
// By setting the local address to broadcast we simulate the
|
||||
// typical scenario when client's request was send to broadcast
|
||||
// address and server by default used it as a source address
|
||||
// in its response. The send() function should be able to detect
|
||||
// it and correct the source address.
|
||||
pkt->setLocalAddr(IOAddress("255.255.255.255"));
|
||||
pkt->setRemoteAddr(IOAddress("127.0.0.1"));
|
||||
pkt->setRemotePort(PORT);
|
||||
pkt->setLocalPort(PORT + 1);
|
||||
pkt->setIndex(ifindex_);
|
||||
pkt->setIface(ifname_);
|
||||
pkt->setHops(6);
|
||||
pkt->setSecs(42);
|
||||
pkt->setCiaddr(IOAddress("192.0.2.1"));
|
||||
pkt->setSiaddr(IOAddress("192.0.2.2"));
|
||||
pkt->setYiaddr(IOAddress("192.0.2.3"));
|
||||
pkt->setGiaddr(IOAddress("192.0.2.4"));
|
||||
|
||||
// Create the on-wire data.
|
||||
ASSERT_NO_THROW(pkt->pack());
|
||||
|
||||
// Packet will be sent over loopback interface.
|
||||
Iface iface(ifname_, ifindex_);
|
||||
IOAddress addr("127.0.0.1");
|
||||
@@ -183,27 +112,29 @@ TEST_F(PktFilterLPFTest, DISABLED_send) {
|
||||
// Open socket. We don't check that the socket has appropriate
|
||||
// options and family set because we have checked that in the
|
||||
// openSocket test already.
|
||||
socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
|
||||
ASSERT_GE(socket_, 0);
|
||||
|
||||
sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
|
||||
|
||||
ASSERT_GE(sock_info_.sockfd_, 0);
|
||||
|
||||
// Send the packet over the socket.
|
||||
ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
|
||||
ASSERT_NO_THROW(pkt_filter.send(iface, sock_info_.sockfd_, test_message_));
|
||||
|
||||
// Read the data from socket.
|
||||
fd_set readfds;
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(socket_, &readfds);
|
||||
|
||||
FD_SET(sock_info_.sockfd_, &readfds);
|
||||
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = 5;
|
||||
timeout.tv_usec = 0;
|
||||
int result = select(socket_ + 1, &readfds, NULL, NULL, &timeout);
|
||||
int result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout);
|
||||
// We should receive some data from loopback interface.
|
||||
ASSERT_GT(result, 0);
|
||||
|
||||
// Get the actual data.
|
||||
uint8_t rcv_buf[RECV_BUF_SIZE];
|
||||
result = recv(socket_, rcv_buf, RECV_BUF_SIZE, 0);
|
||||
result = recv(sock_info_.sockfd_, rcv_buf, RECV_BUF_SIZE, 0);
|
||||
ASSERT_GT(result, 0);
|
||||
|
||||
Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0));
|
||||
@@ -223,48 +154,15 @@ TEST_F(PktFilterLPFTest, DISABLED_send) {
|
||||
// Parse the packet.
|
||||
ASSERT_NO_THROW(rcvd_pkt->unpack());
|
||||
|
||||
// Verify that the received packet matches sent packet.
|
||||
EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
|
||||
EXPECT_EQ(pkt->getOp(), rcvd_pkt->getOp());
|
||||
EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
|
||||
EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
|
||||
EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
|
||||
EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
|
||||
EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
|
||||
EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
|
||||
EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
|
||||
EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
|
||||
EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
|
||||
EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
|
||||
EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
|
||||
// Check if the received message is correct.
|
||||
testRcvdMessage(rcvd_pkt);
|
||||
}
|
||||
|
||||
// This test verifies correctness of reception of the DHCP packet over
|
||||
// raw socket, whereby all IP stack headers are hand-crafted.
|
||||
TEST_F(PktFilterLPFTest, DISABLED_receive) {
|
||||
|
||||
// Let's create a DHCPv4 packet.
|
||||
Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
|
||||
ASSERT_TRUE(pkt);
|
||||
|
||||
// Set required fields.
|
||||
pkt->setLocalAddr(IOAddress("127.0.0.1"));
|
||||
pkt->setRemoteAddr(IOAddress("127.0.0.1"));
|
||||
pkt->setRemotePort(PORT);
|
||||
pkt->setLocalPort(PORT + 1);
|
||||
pkt->setIndex(ifindex_);
|
||||
pkt->setIface(ifname_);
|
||||
pkt->setHops(6);
|
||||
pkt->setSecs(42);
|
||||
pkt->setCiaddr(IOAddress("192.0.2.1"));
|
||||
pkt->setSiaddr(IOAddress("192.0.2.2"));
|
||||
pkt->setYiaddr(IOAddress("192.0.2.3"));
|
||||
pkt->setGiaddr(IOAddress("192.0.2.4"));
|
||||
|
||||
// Create the on-wire data.
|
||||
ASSERT_NO_THROW(pkt->pack());
|
||||
|
||||
// Packet will be sent over loopback interface.
|
||||
// Packet will be received over loopback interface.
|
||||
Iface iface(ifname_, ifindex_);
|
||||
IOAddress addr("127.0.0.1");
|
||||
|
||||
@@ -273,35 +171,22 @@ TEST_F(PktFilterLPFTest, DISABLED_receive) {
|
||||
// Open socket. We don't check that the socket has appropriate
|
||||
// options and family set because we have checked that in the
|
||||
// openSocket test already.
|
||||
socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
|
||||
ASSERT_GE(socket_, 0);
|
||||
sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
|
||||
ASSERT_GE(sock_info_.sockfd_, 0);
|
||||
|
||||
// Send the packet over the socket.
|
||||
ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
|
||||
// Send DHCPv4 message to the local loopback address and server's port.
|
||||
sendMessage();
|
||||
|
||||
// Receive the packet.
|
||||
SocketInfo socket_info(socket_, IOAddress("127.0.0.1"), PORT);
|
||||
Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, socket_info);
|
||||
// Receive the packet using LPF packet filter.
|
||||
Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, sock_info_);
|
||||
// Check that the packet has been correctly received.
|
||||
ASSERT_TRUE(rcvd_pkt);
|
||||
|
||||
// Parse the packet.
|
||||
ASSERT_NO_THROW(rcvd_pkt->unpack());
|
||||
|
||||
// Verify that the received packet matches sent packet.
|
||||
EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
|
||||
EXPECT_EQ(pkt->getOp(), rcvd_pkt->getOp());
|
||||
EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
|
||||
EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
|
||||
EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
|
||||
EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
|
||||
EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
|
||||
EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
|
||||
EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
|
||||
EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
|
||||
EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
|
||||
EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
|
||||
EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
|
||||
// Check if the received message is correct.
|
||||
testRcvdMessage(rcvd_pkt);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
194
src/lib/dhcp/tests/pkt_filter_test_utils.cc
Normal file
194
src/lib/dhcp/tests/pkt_filter_test_utils.cc
Normal file
@@ -0,0 +1,194 @@
|
||||
// Copyright (C) 2013 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 <asiolink/io_address.h>
|
||||
#include <dhcp/pkt4.h>
|
||||
#include <dhcp/tests/pkt_filter_test_utils.h>
|
||||
|
||||
using namespace isc::asiolink;
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
namespace test {
|
||||
|
||||
PktFilterTest::PktFilterTest(const uint16_t port)
|
||||
: port_(port),
|
||||
sock_info_(isc::asiolink::IOAddress("127.0.0.1"), port, -1, -1),
|
||||
send_msg_sock_(-1) {
|
||||
// Initialize ifname_ and ifindex_.
|
||||
loInit();
|
||||
// Initialize test_message_.
|
||||
initTestMessage();
|
||||
}
|
||||
|
||||
PktFilterTest::~PktFilterTest() {
|
||||
// Cleanup after each test. This guarantees
|
||||
// that the sockets do not hang after a test.
|
||||
if (sock_info_.sockfd_ >= 0) {
|
||||
close(sock_info_.sockfd_);
|
||||
}
|
||||
if (sock_info_.fallbackfd_ >=0) {
|
||||
close(sock_info_.fallbackfd_);
|
||||
}
|
||||
if (send_msg_sock_ >= 0) {
|
||||
close(send_msg_sock_);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PktFilterTest::initTestMessage() {
|
||||
// Let's create a DHCPv4 message instance.
|
||||
test_message_.reset(new Pkt4(DHCPOFFER, 0));
|
||||
|
||||
// Set required fields.
|
||||
test_message_->setLocalAddr(IOAddress("127.0.0.1"));
|
||||
test_message_->setRemoteAddr(IOAddress("127.0.0.1"));
|
||||
test_message_->setRemotePort(port_);
|
||||
test_message_->setLocalPort(port_ + 1);
|
||||
test_message_->setIndex(ifindex_);
|
||||
test_message_->setIface(ifname_);
|
||||
test_message_->setHops(6);
|
||||
test_message_->setSecs(42);
|
||||
test_message_->setCiaddr(IOAddress("192.0.2.1"));
|
||||
test_message_->setSiaddr(IOAddress("192.0.2.2"));
|
||||
test_message_->setYiaddr(IOAddress("192.0.2.3"));
|
||||
test_message_->setGiaddr(IOAddress("192.0.2.4"));
|
||||
|
||||
try {
|
||||
test_message_->pack();
|
||||
} catch (const isc::Exception& ex) {
|
||||
ADD_FAILURE() << "failed to create test message for PktFilterTest";
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PktFilterTest::loInit() {
|
||||
if (if_nametoindex("lo") > 0) {
|
||||
ifname_ = "lo";
|
||||
ifindex_ = if_nametoindex("lo");
|
||||
|
||||
} else if (if_nametoindex("lo0") > 0) {
|
||||
ifname_ = "lo0";
|
||||
ifindex_ = if_nametoindex("lo0");
|
||||
|
||||
} else {
|
||||
std::cout << "Failed to detect loopback interface. Neither "
|
||||
<< "lo nor lo0 worked. Giving up." << std::endl;
|
||||
FAIL();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PktFilterTest::sendMessage() {
|
||||
|
||||
// Packet will be sent over loopback interface.
|
||||
Iface iface(ifname_, ifindex_);
|
||||
IOAddress addr("127.0.0.1");
|
||||
|
||||
struct sockaddr_in addr4;
|
||||
memset(&addr4, 0, sizeof(sockaddr));
|
||||
addr4.sin_family = AF_INET;
|
||||
addr4.sin_port = htons(port_ + 1);
|
||||
|
||||
send_msg_sock_ = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
ASSERT_GE(send_msg_sock_, 0);
|
||||
|
||||
ASSERT_GE(bind(send_msg_sock_, (struct sockaddr *)&addr4,
|
||||
sizeof(addr4)), 0);
|
||||
|
||||
struct sockaddr_in dest_addr4;
|
||||
memset(&dest_addr4, 0, sizeof(sockaddr));
|
||||
dest_addr4.sin_family = AF_INET;
|
||||
dest_addr4.sin_port = htons(port_);
|
||||
ASSERT_EQ(sendto(send_msg_sock_, test_message_->getBuffer().getData(),
|
||||
test_message_->getBuffer().getLength(), 0,
|
||||
reinterpret_cast<struct sockaddr*>(&dest_addr4),
|
||||
sizeof(sockaddr)), test_message_->getBuffer().getLength());
|
||||
close(send_msg_sock_);
|
||||
send_msg_sock_ = -1;
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
PktFilterTest::testDgramSocket(const int sock) const {
|
||||
// Check that socket has been opened.
|
||||
ASSERT_GE(sock, 0);
|
||||
|
||||
// Verify that the socket belongs to AF_INET family.
|
||||
sockaddr_in sock_address;
|
||||
socklen_t sock_address_len = sizeof(sock_address);
|
||||
ASSERT_EQ(0, getsockname(sock,
|
||||
reinterpret_cast<sockaddr*>(&sock_address),
|
||||
&sock_address_len));
|
||||
EXPECT_EQ(AF_INET, sock_address.sin_family);
|
||||
|
||||
// Verify that the socket is bound the appropriate address.
|
||||
const std::string bind_addr(inet_ntoa(sock_address.sin_addr));
|
||||
EXPECT_EQ("127.0.0.1", bind_addr);
|
||||
|
||||
// Verify that the socket is bound to appropriate port.
|
||||
EXPECT_EQ(port_, ntohs(sock_address.sin_port));
|
||||
|
||||
// Verify that the socket has SOCK_DGRAM type.
|
||||
int sock_type;
|
||||
socklen_t sock_type_len = sizeof(sock_type);
|
||||
ASSERT_EQ(0, getsockopt(sock, SOL_SOCKET, SO_TYPE,
|
||||
&sock_type, &sock_type_len));
|
||||
EXPECT_EQ(SOCK_DGRAM, sock_type);
|
||||
}
|
||||
|
||||
void
|
||||
PktFilterTest::testRcvdMessage(const Pkt4Ptr& rcvd_msg) const {
|
||||
EXPECT_EQ(test_message_->getHops(), rcvd_msg->getHops());
|
||||
EXPECT_EQ(test_message_->getOp(), rcvd_msg->getOp());
|
||||
EXPECT_EQ(test_message_->getSecs(), rcvd_msg->getSecs());
|
||||
EXPECT_EQ(test_message_->getFlags(), rcvd_msg->getFlags());
|
||||
EXPECT_EQ(test_message_->getCiaddr(), rcvd_msg->getCiaddr());
|
||||
EXPECT_EQ(test_message_->getSiaddr(), rcvd_msg->getSiaddr());
|
||||
EXPECT_EQ(test_message_->getYiaddr(), rcvd_msg->getYiaddr());
|
||||
EXPECT_EQ(test_message_->getGiaddr(), rcvd_msg->getGiaddr());
|
||||
EXPECT_EQ(test_message_->getTransid(), rcvd_msg->getTransid());
|
||||
EXPECT_TRUE(test_message_->getSname() == rcvd_msg->getSname());
|
||||
EXPECT_TRUE(test_message_->getFile() == rcvd_msg->getFile());
|
||||
EXPECT_EQ(test_message_->getHtype(), rcvd_msg->getHtype());
|
||||
EXPECT_EQ(test_message_->getHlen(), rcvd_msg->getHlen());
|
||||
}
|
||||
|
||||
bool
|
||||
PktFilterStub::isDirectResponseSupported() const {
|
||||
return (true);
|
||||
}
|
||||
|
||||
SocketInfo
|
||||
PktFilterStub::openSocket(const Iface&,
|
||||
const isc::asiolink::IOAddress& addr,
|
||||
const uint16_t port, const bool, const bool) {
|
||||
return (SocketInfo(addr, port, 0));
|
||||
}
|
||||
|
||||
Pkt4Ptr
|
||||
PktFilterStub::receive(const Iface&, const SocketInfo&) {
|
||||
return Pkt4Ptr();
|
||||
}
|
||||
|
||||
int
|
||||
PktFilterStub::send(const Iface&, uint16_t, const Pkt4Ptr&) {
|
||||
return (0);
|
||||
}
|
||||
|
||||
|
||||
} // end of isc::dhcp::test namespace
|
||||
} // end of isc::dhcp namespace
|
||||
} // end of isc namespace
|
167
src/lib/dhcp/tests/pkt_filter_test_utils.h
Normal file
167
src/lib/dhcp/tests/pkt_filter_test_utils.h
Normal file
@@ -0,0 +1,167 @@
|
||||
// Copyright (C) 2013 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 PKT_FILTER_TEST_UTILS_H
|
||||
#define PKT_FILTER_TEST_UTILS_H
|
||||
|
||||
#include <asiolink/io_address.h>
|
||||
#include <dhcp/iface_mgr.h>
|
||||
#include <dhcp/pkt_filter.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace isc {
|
||||
namespace dhcp {
|
||||
namespace test {
|
||||
|
||||
/// @brief Test fixture class for testing classes derived from PktFilter class.
|
||||
///
|
||||
/// This class implements a simple algorithm checking presence of the loopback
|
||||
/// interface and initializing its index. It assumes that the loopback interface
|
||||
/// name is one of 'lo' or 'lo0'. If none of those interfaces is found, the
|
||||
/// constructor will report a failure.
|
||||
///
|
||||
/// @todo The interface detection algorithm should be more generic. This will
|
||||
/// be possible once the cross-OS interface detection is implemented.
|
||||
class PktFilterTest : public ::testing::Test {
|
||||
public:
|
||||
|
||||
/// @brief Constructor
|
||||
///
|
||||
/// This constructor initializes sock_info_ structure to a default value.
|
||||
/// The socket descriptors should be set to a negative value to indicate
|
||||
/// that no socket has been opened. Specific tests will reinitialize this
|
||||
/// structure with the values of the open sockets. For non-negative socket
|
||||
/// descriptors, the class destructor will close associated sockets.
|
||||
PktFilterTest(const uint16_t port);
|
||||
|
||||
/// @brief Destructor
|
||||
///
|
||||
/// Closes open sockets (if any).
|
||||
virtual ~PktFilterTest();
|
||||
|
||||
/// @brief Initializes DHCPv4 message used by tests.
|
||||
void initTestMessage();
|
||||
|
||||
/// @brief Detect loopback interface.
|
||||
///
|
||||
/// @todo this function will be removed once cross-OS interface
|
||||
/// detection is implemented
|
||||
void loInit();
|
||||
|
||||
/// @brief Sends a single DHCPv4 message to the loopback address.
|
||||
///
|
||||
/// This function opens a datagram socket and binds it to the local loopback
|
||||
/// address and client port. The client's port is assumed to be port_ + 1.
|
||||
/// The send_msg_sock_ member holds the socket descriptor so as the socket
|
||||
/// is closed automatically in the destructor. If the function succeeds to
|
||||
/// send a DHCPv4 message, the socket is closed so as the function can be
|
||||
/// called again within the same test.
|
||||
void sendMessage();
|
||||
|
||||
/// @brief Test that the datagram socket is opened correctly.
|
||||
///
|
||||
/// This function is used by multiple tests.
|
||||
///
|
||||
/// @param sock A descriptor of the open socket.
|
||||
void testDgramSocket(const int sock) const;
|
||||
|
||||
/// @brief Checks if the received message matches the test_message_.
|
||||
///
|
||||
/// @param rcvd_msg An instance of the message to be tested.
|
||||
void testRcvdMessage(const Pkt4Ptr& rcvd_msg) const;
|
||||
|
||||
std::string ifname_; ///< Loopback interface name
|
||||
uint16_t ifindex_; ///< Loopback interface index.
|
||||
uint16_t port_; ///< A port number used for the test.
|
||||
isc::dhcp::SocketInfo sock_info_; ///< A structure holding socket info.
|
||||
int send_msg_sock_; ///< Holds a descriptor of the socket used by
|
||||
///< sendMessage function.
|
||||
Pkt4Ptr test_message_; ///< A DHCPv4 message used by tests.
|
||||
|
||||
};
|
||||
|
||||
/// @brief A stub implementation of the PktFilter class.
|
||||
///
|
||||
/// This class implements abstract methods of the @c isc::dhcp::test::PktFilter
|
||||
/// class. It is used by unit tests, which test protected methods of the
|
||||
/// @c isc::dhcp::test::PktFilter class. The implemented abstract methods are
|
||||
/// no-op.
|
||||
class PktFilterStub : public PktFilter {
|
||||
public:
|
||||
|
||||
/// @brief Checks if the direct DHCPv4 response is supported.
|
||||
///
|
||||
/// This function checks if the direct response capability is supported,
|
||||
/// i.e. if the server can respond to the client which doesn't have an
|
||||
/// address yet. For this dummy class, the true is alaways returned.
|
||||
///
|
||||
/// @return always true.
|
||||
virtual bool isDirectResponseSupported() const;
|
||||
|
||||
/// @brief Simulate opening of the socket.
|
||||
///
|
||||
/// This function simulates opening a primary socket. In reality, it doesn't
|
||||
/// open a socket but the socket descriptor returned in the SocketInfo
|
||||
/// structure is always set to 0.
|
||||
///
|
||||
/// @param iface An interface descriptor.
|
||||
/// @param addr Address on the interface to be used to send packets.
|
||||
/// @param port Port number to bind socket to.
|
||||
/// @param receive_bcast A flag which indicates if socket should be
|
||||
/// configured to receive broadcast packets (if true).
|
||||
/// @param send_bcast A flag which indicates if the socket should be
|
||||
/// configured to send broadcast packets (if true).
|
||||
///
|
||||
/// @note All parameters are ignored.
|
||||
///
|
||||
/// @return A SocketInfo structure with the socket descriptor set to 0. The
|
||||
/// fallback socket descriptor is set to a negative value.
|
||||
virtual SocketInfo openSocket(const Iface& iface,
|
||||
const isc::asiolink::IOAddress& addr,
|
||||
const uint16_t port, const bool, const bool);
|
||||
|
||||
/// @brief Simulate reception of the DHCPv4 message.
|
||||
///
|
||||
/// @param iface An interface to be used to receive DHCPv4 message.
|
||||
/// @param sock_info A descriptor of the primary and fallback sockets.
|
||||
///
|
||||
/// @note All parameters are ignored.
|
||||
///
|
||||
/// @return always a NULL object.
|
||||
virtual Pkt4Ptr receive(const Iface& iface, const SocketInfo& sock_info);
|
||||
|
||||
/// @brief Simulates sending a DHCPv4 message.
|
||||
///
|
||||
/// This function does nothing.
|
||||
///
|
||||
/// @param iface An interface to be used to send DHCPv4 message.
|
||||
/// @param port A port used to send a message.
|
||||
/// @param pkt A DHCPv4 to be sent.
|
||||
///
|
||||
/// @note All parameters are ignored.
|
||||
///
|
||||
/// @return 0.
|
||||
virtual int send(const Iface& iface, uint16_t port, const Pkt4Ptr& pkt);
|
||||
|
||||
// Change the scope of the protected function so as they can be unit tested.
|
||||
using PktFilter::openFallbackSocket;
|
||||
|
||||
};
|
||||
|
||||
|
||||
}; // end of isc::dhcp::test namespace
|
||||
}; // end of isc::dhcp namespace
|
||||
}; // end of isc namespace
|
||||
|
||||
#endif // PKT_FILTER_TEST_UTILS_H
|
74
src/lib/dhcp/tests/pkt_filter_unittest.cc
Normal file
74
src/lib/dhcp/tests/pkt_filter_unittest.cc
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright (C) 2013 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 <config.h>
|
||||
#include <asiolink/io_address.h>
|
||||
#include <dhcp/iface_mgr.h>
|
||||
#include <dhcp/tests/pkt_filter_test_utils.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace isc::asiolink;
|
||||
using namespace isc::dhcp;
|
||||
using namespace isc::dhcp::test;
|
||||
|
||||
namespace {
|
||||
|
||||
/// Port number used by tests.
|
||||
const uint16_t PORT = 10067;
|
||||
|
||||
class PktFilterBaseClassTest : public isc::dhcp::test::PktFilterTest {
|
||||
public:
|
||||
/// @brief Constructor
|
||||
///
|
||||
/// Does nothing but setting up the UDP port for the test.
|
||||
PktFilterBaseClassTest() : PktFilterTest(PORT) {
|
||||
}
|
||||
};
|
||||
|
||||
// This test verifies that the fallback socket is successfuly opened and
|
||||
// bound using the protected function of the PktFilter class.
|
||||
TEST_F(PktFilterBaseClassTest, openFallbackSocket) {
|
||||
// Open socket using the function under test. Note that, we don't have to
|
||||
// close the socket on our own because the test fixture constructor
|
||||
// will handle it.
|
||||
PktFilterStub pkt_filter;
|
||||
ASSERT_NO_THROW(sock_info_.fallbackfd_ =
|
||||
pkt_filter.openFallbackSocket(IOAddress("127.0.0.1"), PORT))
|
||||
<< "Failed to open fallback socket.";
|
||||
|
||||
// Test that the socket has been successfully created.
|
||||
testDgramSocket(sock_info_.fallbackfd_);
|
||||
|
||||
// In addition, we should check that the fallback socket is non-blocking.
|
||||
int sock_flags = fcntl(sock_info_.fallbackfd_, F_GETFL);
|
||||
EXPECT_EQ(O_NONBLOCK, sock_flags & O_NONBLOCK)
|
||||
<< "Fallback socket is blocking, it should be non-blocking - check"
|
||||
" fallback socket flags (fcntl).";
|
||||
|
||||
// Now that we have the socket open, let's try to open another one. This
|
||||
// should cause a binding error.
|
||||
int another_sock = -1;
|
||||
EXPECT_THROW(another_sock =
|
||||
pkt_filter.openFallbackSocket(IOAddress("127.0.0.1"), PORT),
|
||||
isc::dhcp::SocketConfigError)
|
||||
<< "it should be not allowed to open and bind two fallback sockets"
|
||||
" to the same address and port. Surprisingly, the socket bound.";
|
||||
// Hard to believe we managed to open another socket. But if so, we have
|
||||
// to close it to prevent a resource leak.
|
||||
if (another_sock >= 0) {
|
||||
close(another_sock);
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
@@ -47,7 +47,7 @@ namespace perfdhcp {
|
||||
bool TestControl::interrupted_ = false;
|
||||
|
||||
TestControl::TestControlSocket::TestControlSocket(const int socket) :
|
||||
SocketInfo(socket, asiolink::IOAddress("127.0.0.1"), 0),
|
||||
SocketInfo(asiolink::IOAddress("127.0.0.1"), 0, socket),
|
||||
ifindex_(0), valid_(true) {
|
||||
try {
|
||||
initSocketData();
|
||||
|
Reference in New Issue
Block a user