Browse Source

[3242] Created a test library to create fake interfaces in IfaceMgr.

Marcin Siodelski 11 years ago
parent
commit
aed723721a

+ 11 - 0
src/lib/dhcp/tests/Makefile.am

@@ -24,6 +24,16 @@ TESTS_ENVIRONMENT = \
 
 
 TESTS =
 TESTS =
 if HAVE_GTEST
 if HAVE_GTEST
+
+lib_LTLIBRARIES = libdhcptest.la
+
+libdhcptest_la_SOURCES  = iface_mgr_test_config.cc iface_mgr_test_config.h
+libdhcptest_la_SOURCES  += pkt_filter_test_stub.cc pkt_filter_test_stub.h
+libdhcptest_la_CXXFLAGS  = $(AM_CXXFLAGS)
+libdhcptest_la_CPPFLAGS  = $(AM_CPPFLAGS)
+libdhcptest_la_LDFLAGS   = $(AM_LDFLAGS)
+libdhcptest_la_LIBADD    = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+
 TESTS += libdhcp++_unittests
 TESTS += libdhcp++_unittests
 
 
 libdhcp___unittests_SOURCES  = run_unittests.cc
 libdhcp___unittests_SOURCES  = run_unittests.cc
@@ -74,6 +84,7 @@ libdhcp___unittests_CXXFLAGS += -Wno-unused-variable -Wno-unused-parameter
 endif
 endif
 
 
 libdhcp___unittests_LDADD  = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 libdhcp___unittests_LDADD  = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+libdhcp___unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
 libdhcp___unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 libdhcp___unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 libdhcp___unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 libdhcp___unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 libdhcp___unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 libdhcp___unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la

+ 124 - 0
src/lib/dhcp/tests/iface_mgr_test_config.cc

@@ -0,0 +1,124 @@
+// Copyright (C) 2014 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 <dhcp/pkt_filter.h>
+#include <dhcp/pkt_filter_inet.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/tests/pkt_filter_test_stub.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+IfaceMgrTestConfig::IfaceMgrTestConfig(const bool default_config) {
+    IfaceMgr::instance().closeSockets();
+    IfaceMgr::instance().clearIfaces();
+    IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterTestStub()));
+
+    // Create default set of fake interfaces: lo, eth0 and eth1.
+    if (default_config) {
+        createIfaces();
+    }
+}
+
+IfaceMgrTestConfig::~IfaceMgrTestConfig() {
+    IfaceMgr::instance().closeSockets();
+    IfaceMgr::instance().clearIfaces();
+    IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet()));
+
+    IfaceMgr::instance().detectIfaces();
+}
+
+void
+IfaceMgrTestConfig::addAddress(const std::string& iface_name,
+                               const IOAddress& address) {
+    Iface* iface = IfaceMgr::instance().getIface(iface_name);
+    if (iface == NULL) {
+        isc_throw(isc::BadValue, "interface '" << iface_name
+                  << "' doesn't exist");
+    }
+    iface->addAddress(address);
+}
+
+void
+IfaceMgrTestConfig::addIface(const Iface& iface) {
+    IfaceMgr::instance().addInterface(iface);
+}
+
+void
+IfaceMgrTestConfig::addIface(const std::string& name, const int ifindex) {
+    IfaceMgr::instance().addInterface(createIface(name, ifindex));
+}
+
+Iface
+IfaceMgrTestConfig::createIface(const std::string &name, const int ifindex) {
+    Iface iface(name, ifindex);
+    if (name == "lo") {
+        iface.flag_loopback_ = true;
+    }
+    iface.flag_multicast_ = true;
+    // On BSD systems, the SO_BINDTODEVICE option is not supported.
+    // Therefore the IfaceMgr will throw an exception on attempt to
+    // open sockets on more than one broadcast-capable interface at
+    // the same time. In order to prevent this error, we mark all
+    // interfaces broadcast-incapable for unit testing.
+    iface.flag_broadcast_ = false;
+    iface.flag_up_ = true;
+    iface.flag_running_ = true;
+    iface.inactive4_ = false;
+    iface.inactive6_ = false;
+    return (iface);
+}
+
+void
+IfaceMgrTestConfig::createIfaces() {
+    // local loopback
+    addIface("lo", 0);
+    addAddress("lo", IOAddress("127.0.0.1"));
+    addAddress("lo", IOAddress("::1"));
+    // eth0
+    addIface("eth0", 1);
+    addAddress("eth0", IOAddress("10.0.0.1"));
+    addAddress("eth0", IOAddress("fe80::3a60:77ff:fed5:cdef"));
+    addAddress("eth0", IOAddress("2001:db8:1::1"));
+    // eth1
+    addIface("eth1", 2);
+    addAddress("eth1", IOAddress("192.0.2.3"));
+    addAddress("eth1", IOAddress("fe80::3a60:77ff:fed5:cdef"));
+
+}
+
+void
+IfaceMgrTestConfig::setIfaceFlags(const std::string& name,
+                                  const FlagLoopback& loopback,
+                                  const FlagUp& up,
+                                  const FlagRunning& running,
+                                  const FlagInactive4& inactive4,
+                                  const FlagInactive6& inactive6) {
+    Iface* iface = IfaceMgr::instance().getIface(name);
+    if (iface == NULL) {
+        isc_throw(isc::BadValue, "interface '" << name << "' doesn't exist");
+    }
+    iface->flag_loopback_ = loopback.flag_;
+    iface->flag_up_ = up.flag_;
+    iface->flag_running_ = running.flag_;
+    iface->inactive4_ = inactive4.flag_;
+    iface->inactive6_ = inactive6.flag_;
+}
+
+}
+}
+}

+ 171 - 0
src/lib/dhcp/tests/iface_mgr_test_config.h

@@ -0,0 +1,171 @@
+// Copyright (C) 2014 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 IFACE_MGR_TEST_CONFIG_H
+#define IFACE_MGR_TEST_CONFIG_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+///
+/// @name Set of structures describing interface flags.
+///
+//@{
+/// @brief Structure describing the loopback interface flag.
+struct FlagLoopback {
+    explicit FlagLoopback(bool flag) : flag_(flag) { }
+    bool flag_;
+};
+
+/// @brief Structure describing the up interface flag.
+struct FlagUp {
+    explicit FlagUp(bool flag) : flag_(flag) { }
+    bool flag_;
+};
+
+/// @brief Structure describing the running interface flag.
+struct FlagRunning {
+    explicit FlagRunning(bool flag) : flag_(flag) { }
+    bool flag_;
+};
+
+/// @brief Structure describing the inactive4 interface flag.
+struct FlagInactive4 {
+    explicit FlagInactive4(bool flag) : flag_(flag) { }
+    bool flag_;
+};
+
+/// @brief Structure describing the inactive6 interface flag.
+struct FlagInactive6 {
+    explicit FlagInactive6(bool flag) : flag_(flag) { }
+    bool flag_;
+};
+//@}
+
+/// @brief Convenience class for configuring @c IfaceMgr for unit testing.
+///
+/// This class is used by various unit tests which test the code relaying
+/// on IfaceMgr. The use of this class is not limited to libdhcp++ validation.
+/// There are other libraries and applications (e.g. DHCP servers) which
+/// depend on @c IfaceMgr.
+///
+/// During the normal operation, the @c IfaceMgr detects interfaces present
+/// on the machine where it is running. It also provides the means for
+/// applications to open sockets on these interfaces and perform other
+/// IO operations. This however creates dependency of the applications
+/// using @c IfaceMgr on the physical properties of the system and effectively
+/// makes it very hard to unit test the dependent code.
+///
+/// Unit tests usually require that @c IfaceMgr holds a list of well known
+/// interfaces with the well known set of IP addresses and other properties
+/// (a.k.a. interface flags). The solution which works for many test scenarios
+/// is to provide a set of well known fake interfaces, by bypassing the
+/// standard interface detection procedure and manually adding @c Iface objects
+/// which encapsulate the fake interfaces. As a consequence, it becomes
+/// impossible to test IO operations (e.g. sending packets) because real sockets
+/// can't be opened on these interfaces. The @c PktFilterTestStub class
+/// is used by this class to mimic behavior of IO operations on fake sockets.
+///
+/// This class provides a set of convenience functions that should be called
+/// by unit tests to configure the @c IfaceMgr with fake interfaces.
+class IfaceMgrTestConfig : public boost::noncopyable {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// It closes all sockets opened by @c IfaceMgr and removes all interfaces
+    /// being used by @c IfaceMgr.
+    IfaceMgrTestConfig(const bool default_config = false);
+
+    /// @brief Destructor.
+    ///
+    /// Closes all currently opened sockets, removes current interfaces and
+    /// sets the default packet filtering classes. The default packet filtering
+    /// classes are used for IO operations on real sockets/interfaces.
+    ///
+    /// Destructor also re-detects real interfaces.
+    ~IfaceMgrTestConfig();
+
+    /// @brief Adds new IPv4 or IPv6 address to the interface.
+    ///
+    /// @param iface_name Name of the interface on which new address should
+    /// be configured.
+    /// @param IPv4 or IPv6 address to be configured on the interface.
+    void addAddress(const std::string& iface_name,
+                    const asiolink::IOAddress& address);
+
+    /// @brief Configures new interface for the @c IfaceMgr.
+    ///
+    /// @param iface Object encapsulating interface to be added.
+    void addIface(const Iface& iface);
+
+    /// @brief Configures new interface for the @c IfaceMgr.
+    ///
+    /// @param name Name of the new interface.
+    /// @param ifindex Index for a new interface.
+    void addIface(const std::string& name, const int ifindex);
+
+    /// @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
+    /// - multicast always to true
+    /// - broadcast 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.
+    ///
+    /// @return An object representing interface.
+    static Iface createIface(const std::string& name, const int ifindex);
+
+    /// @brief Creates a default (example) set of fake interfaces.
+    void createIfaces();
+
+    /// @brief Sets various flags on the specified interface.
+    ///
+    /// This function configures interface with new values for flags.
+    ///
+    /// @param loopback Specifies if interface is a loopback interface.
+    /// @param up Specifies if the interface is up.
+    /// @param running Specifies if the interface is running.
+    /// @param inactive4 Specifies if the interface is inactive for V4
+    /// traffic, i.e. @c IfaceMgr opens V4 sockets on this interface.
+    /// @param inactive6 Specifies if the interface is inactive for V6
+    /// traffic, i.e. @c IfaceMgr opens V6 sockets on this interface.
+    void setIfaceFlags(const std::string& name,
+                       const FlagLoopback& loopback,
+                       const FlagUp& up,
+                       const FlagRunning& running,
+                       const FlagInactive4& inactive4,
+                       const FlagInactive6& inactive6);
+
+};
+
+};
+};
+};
+
+#endif // IFACE_MGR_TEST_CONFIG_H

+ 45 - 0
src/lib/dhcp/tests/pkt_filter_test_stub.cc

@@ -0,0 +1,45 @@
+// Copyright (C) 2014 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 <dhcp/tests/pkt_filter_test_stub.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+bool
+PktFilterTestStub::isDirectResponseSupported() const {
+    return (true);
+}
+
+SocketInfo
+PktFilterTestStub::openSocket(const Iface&,
+           const isc::asiolink::IOAddress& addr,
+           const uint16_t port, const bool, const bool) {
+    return (SocketInfo(addr, port, 0));
+}
+
+Pkt4Ptr
+PktFilterTestStub::receive(const Iface&, const SocketInfo&) {
+    return Pkt4Ptr();
+}
+
+int
+PktFilterTestStub::send(const Iface&, uint16_t, const Pkt4Ptr&) {
+    return (0);
+}
+
+}
+}
+}

+ 98 - 0
src/lib/dhcp/tests/pkt_filter_test_stub.h

@@ -0,0 +1,98 @@
+// Copyright (C) 2014 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_STUB_H
+#define PKT_FILTER_TEST_STUB_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter.h>
+#include <dhcp/pkt4.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief A stub implementation of the PktFilter class.
+///
+/// This class implements abstract methods of the @c isc::dhcp::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 PktFilterTestStub : 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;
+};
+
+} // namespace isc::dhcp::test
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER_TEST_STUB_H