]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#4212] added more UTs master
authorRazvan Becheriu <razvan@isc.org>
Thu, 15 Jan 2026 13:33:14 +0000 (15:33 +0200)
committerRazvan Becheriu <razvan@isc.org>
Tue, 14 Apr 2026 15:08:24 +0000 (18:08 +0300)
src/lib/dhcp/iface_mgr.cc
src/lib/dhcp/tests/iface_mgr_unittest.cc

index f764cdf1067de711348725685c9972cc5eb80784..58f82b516f04d6a3a7b09990b69ac9ffe7f378bc 100644 (file)
@@ -1311,9 +1311,6 @@ Pkt4Ptr IfaceMgr::receive4Indirect(uint32_t timeout_sec, uint32_t timeout_usec /
             // in IfaceMgr
             ex_sock->callback_(ex_sock->socket_);
         }
-        if (found) {
-            return (Pkt4Ptr());
-        }
     }
 
     // If we're here it should only be because there are DHCP packets waiting.
@@ -1428,9 +1425,6 @@ Pkt4Ptr IfaceMgr::receive4Direct(uint32_t timeout_sec, uint32_t timeout_usec /*
         // in IfaceMgr
         ex_sock->callback_(ex_sock->socket_);
     }
-    if (found) {
-        return (Pkt4Ptr());
-    }
 
     // Let's find out which interface/socket has the data
     boost::scoped_ptr<SocketInfo> candidate;
@@ -1601,9 +1595,6 @@ IfaceMgr::receive6Direct(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ )
         // in IfaceMgr
         ex_sock->callback_(ex_sock->socket_);
     }
-    if (found) {
-        return (Pkt6Ptr());
-    }
 
     // Let's find out which interface/socket has the data
     boost::scoped_ptr<SocketInfo> candidate;
@@ -1779,9 +1770,6 @@ IfaceMgr::receive6Indirect(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
             // in IfaceMgr
             ex_sock->callback_(ex_sock->socket_);
         }
-        if (found) {
-            return (Pkt6Ptr());
-        }
     }
 
     // If we're here it should only be because there are DHCP packets waiting.
@@ -2076,7 +2064,7 @@ IfaceMgr::receiveDHCP4Packet(Iface& iface, const SocketInfo& socket_info) {
         pkt = packet_filter_->receive(iface, socket_info);
     } catch (const std::exception& ex) {
         std::lock_guard<std::mutex> lk(receiver_mutex_);
-        dhcp_receiver_->setError(strerror(errno));
+        dhcp_receiver_->setError(ex.what());
     } catch (...) {
         std::lock_guard<std::mutex> lk(receiver_mutex_);
         dhcp_receiver_->setError("packet filter receive() failed");
index 25835ad841df8707bd70b0dce379d2a13cef3829..63a3f86a3d53a525767b35ca07d144815ae1af0b 100644 (file)
@@ -33,6 +33,8 @@
 #include <unordered_map>
 
 #include <arpa/inet.h>
+#include <sys/resource.h>
+#include <sys/types.h>
 #include <unistd.h>
 
 using namespace std;
@@ -47,6 +49,10 @@ namespace ph = std::placeholders;
 
 namespace {
 
+typedef boost::hash<Pkt4Ptr> hash_pkt4;
+
+typedef boost::hash<Pkt6Ptr> hash_pkt6;
+
 // Note this is for the *real* loopback interface, *not* the fake one.
 // So in tests using it you have LOOPBACK_NAME, LOOPBACK_INDEX and
 // no "eth0" nor "eth1". In tests not using it you can have "lo", LO_INDEX,
@@ -204,694 +210,696 @@ public:
     bool open_socket_called_;
 };
 
-class NakedIfaceMgr: public IfaceMgr {
-    // "Naked" Interface Manager, exposes internal fields
+class PktFilterIfaceSocketTest {
 public:
-
     /// @brief Constructor.
-    NakedIfaceMgr() {
-        loDetect();
-    }
-
-    /// @brief detects name of the loopback interface
     ///
-    /// This method detects name of the loopback interface.
-    static void loDetect() {
-        // Poor man's interface detection.  It will go away as soon as proper
-        // interface detection is implemented
-        if (if_nametoindex("lo") > 0) {
-            snprintf(LOOPBACK_NAME, BUF_SIZE - 1, "lo");
-        } else if (if_nametoindex("lo0") > 0) {
-            snprintf(LOOPBACK_NAME, BUF_SIZE - 1, "lo0");
-        } else {
-            cout << "Failed to detect loopback interface. Neither "
-                 << "lo nor lo0 worked. I give up." << endl;
-            FAIL();
-        }
-        LOOPBACK_INDEX = if_nametoindex(LOOPBACK_NAME);
-    }
+    /// @param ready_on_send Flag which indicates if socket should be marked as
+    /// readReady when calling @ref send.
+    /// @param clear_on_read Flag which indicates is socket should be unmarked as
+    /// readReady when calling @ref receive.
+    PktFilterIfaceSocketTest(bool ready_on_send = true, bool clear_on_read = true);
 
-    /// @brief Returns the collection of existing interfaces.
-    IfaceCollection& getIfacesLst() { return (ifaces_); }
+    /// @brief Destructor.
+    virtual ~PktFilterIfaceSocketTest();
 
-    /// @brief This function creates fictitious interfaces with fictitious
-    /// addresses.
+    /// @brief Simulate opening of the socket.
     ///
-    /// 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
-        IfacePtr lo = createIface("lo", LO_INDEX);
-        lo->addAddress(IOAddress("127.0.0.1"));
-        lo->addAddress(IOAddress("::1"));
-        ifaces_.push_back(lo);
-        // eth0
-        IfacePtr eth0 = createIface("eth0", ETH0_INDEX);
-        eth0->addAddress(IOAddress("10.0.0.1"));
-        eth0->addAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"));
-        eth0->addAddress(IOAddress("2001:db8:1::1"));
-        ifaces_.push_back(eth0);
-        // eth1
-        IfacePtr eth1 = createIface("eth1", ETH1_INDEX);
-        eth1->addAddress(IOAddress("192.0.2.3"));
-        eth1->addAddress(IOAddress("fe80::3a60:77ff:fed5:abcd"));
-        ifaces_.push_back(eth1);
-    }
-
-    /// @brief Create an object representing interface.
+    /// This function simulates opening a primary socket. In reality, it doesn't
+    /// open a socket but uses a pipe which can control if a read event is ready
+    /// or not.
     ///
-    /// 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
+    /// @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).
     ///
-    /// If one needs to modify the default flag settings, the setIfaceFlags
-    /// function should be used.
+    /// @return A SocketInfo structure with the socket descriptor set. The
+    /// fallback socket descriptor is set to a negative value.
+    virtual SocketInfo openSocketCommon(const Iface& iface,
+                                        const isc::asiolink::IOAddress& addr,
+                                        const uint16_t port);
+
+    /// @brief Simulate reception of the DHCPv4/DHCPv6 message.
     ///
-    /// @param name A name of the interface to be created.
-    /// @param ifindex An index of the interface to be created.
+    /// @param sock_info A descriptor of the primary and fallback sockets.
     ///
-    /// @return An object representing interface.
-    static IfacePtr createIface(const std::string& name, const unsigned int ifindex) {
-        IfacePtr iface(new Iface(name, ifindex));
-        if (name == "lo") {
-            iface->flag_loopback_ = true;
-            // Don't open sockets on loopback interface.
-            iface->inactive4_ = true;
-            iface->inactive6_ = true;
-        } else {
-            iface->inactive4_ = false;
-            iface->inactive6_ = false;
-        }
-        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;
-        return (iface);
-    }
+    /// @return the same packet used by @ref send (if any).
+    virtual PktPtr receiveCommon(const SocketInfo& sock_info);
 
-    /// @brief Checks if the specified interface has a socket bound to a
-    /// specified address.
+    /// @brief Simulates sending a DHCPv4/DHCPv6 message.
     ///
-    /// @param iface_name A name of the interface.
-    /// @param addr An address to be checked for binding.
+    /// @param iface An interface to be used to send DHCPv4/DHCPv6 message.
+    /// @param sockfd socket descriptor.
+    /// @param pkt A DHCPv4/DHCPv6 to be sent.
     ///
-    /// @return true if there is a socket bound to the specified address.
-    bool isBound(const std::string& iface_name, const std::string& addr) {
-        IfacePtr iface = getIface(iface_name);
-        if (!iface) {
-            ADD_FAILURE() << "the interface " << iface_name << " doesn't exist";
-            return (false);
-        }
-        const Iface::SocketCollection& sockets = iface->getSockets();
-        for (auto const& sock : sockets) {
-            if (sock.addr_ == IOAddress(addr)) {
-                return (true);
-
-            } else if ((sock.addr_ == IOAddress("::")) &&
-                       (IOAddress(addr).isV6LinkLocal())) {
-                for (auto const& a : iface->getAddresses()) {
-                    if (a.get() == IOAddress(addr)) {
-                        return (true);
-                    }
-                }
-            }
-        }
-        return (false);
-    }
+    /// @return 0.
+    virtual int sendCommon(const Iface& iface, uint16_t sockfd, const PktPtr& pkt);
 
-    /// @brief Modify 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 inactive4,
-                       const bool inactive6) {
-        for (auto const& iface : ifaces_) {
-            if (iface->getName() == name) {
-                iface->flag_loopback_ = loopback;
-                iface->flag_up_ = up;
-                iface->flag_running_ = running;
-                iface->inactive4_ = inactive4;
-                iface->inactive6_ = inactive6;
-            }
-        }
-    }
-};
+    /// @brief Flag which indicates if socket should be marked as
+    /// readReady when calling @ref send.
+    bool ready_on_send_;
 
-volatile bool callback_ok;
-volatile bool callback2_ok;
+    /// @brief Flag which indicates is socket should be unmarked as
+    /// readReady when calling @ref receive.
+    bool clear_on_read_;
 
-void my_callback(int /* fd */) {
-    callback_ok = true;
-}
+    /// @brief The set of opened file descriptors.
+    std::unordered_map<int, int> socket_fds_;
 
-void my_callback2(int /* fd */) {
-    callback2_ok = true;
-}
+    std::unordered_map<int, PktPtr> pkts_;
+};
 
-/// @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 {
+class PktFilter4IfaceSocketTest : public PktFilterIfaceSocketTest, public PktFilter {
 public:
+
     /// @brief Constructor.
-    IfaceMgrTest()
-        : errors_count_(0), kea_event_handler_type_("KEA_EVENT_HANDLER_TYPE") {
-    }
+    ///
+    /// @param ready_on_send Flag which indicates if socket should be marked as
+    /// readReady when calling @ref send.
+    /// @param clear_on_read Flag which indicates is socket should be unmarked as
+    /// readReady when calling @ref receive.
+    PktFilter4IfaceSocketTest(bool ready_on_send = true, bool clear_on_read = true);
 
-    ~IfaceMgrTest() {
-    }
+    /// @brief Destructor.
+    ~PktFilter4IfaceSocketTest() = default;
 
-    /// @brief Tests the number of IPv6 sockets on interface
+    /// @brief Checks if the direct DHCPv4 response is supported.
     ///
-    /// This function checks the expected number of open IPv6 sockets on the
-    /// specified interface. On non-Linux systems, sockets are bound to a
-    /// link-local address and the number of unicast addresses specified.
-    /// On Linux systems, there is two more sockets bound to ff02::1:2
-    /// and ff05::1:3 multicast addresses.
+    /// 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 always returned.
     ///
-    /// @param iface An interface on which sockets are open.
-    /// @param unicast_num A number of unicast addresses bound.
-    /// @param link_local_num A number of link local addresses bound.
-    void checkSocketsCount6(const Iface& iface,
-                            const int unicast_num,
-                            const int link_local_num = 1) {
-        // On local-loopback interface, there should be no sockets.
-        if (iface.flag_loopback_) {
-            ASSERT_TRUE(iface.getSockets().empty())
-                << "expected empty socket set on loopback interface "
-                << iface.getName();
-            return;
-        }
-#if defined OS_LINUX
-        // On Linux, for each link-local address there may be two
-        // additional sockets opened and bound to multicast. These sockets
-        // are only opened if the interface is multicast-capable.
-        ASSERT_EQ(unicast_num
-                  + (iface.flag_multicast_ ? 2 * link_local_num : 0)
-                  + link_local_num,
-                  iface.getSockets().size())
-            << "invalid number of sockets on interface "
-            << iface.getName();
-#else
-        // On non-Linux, there is no additional socket.
-        ASSERT_EQ(unicast_num + link_local_num, iface.getSockets().size())
-            << "invalid number of sockets on interface "
-            << iface.getName();
+    /// @return always true.
+    virtual bool isDirectResponseSupported() const;
 
-#endif
-    }
-
-    // Get the number of IPv4 or IPv6 sockets on the loopback interface
-    int getOpenSocketsCount(const Iface& iface, uint16_t family) const {
-        // Get all sockets.
-        Iface::SocketCollection sockets = iface.getSockets();
-
-        // Loop through sockets and try to find the ones which match the
-        // specified type.
-        int sockets_count = 0;
-        for (auto const& sock : sockets) {
-            // Match found, increase the counter.
-            if (sock.family_ == family) {
-                ++sockets_count;
-            }
-        }
-        return (sockets_count);
-    }
+    /// @brief Check if the socket received time is supported.
+    ///
+    /// If true, then packets received through this filter will include
+    /// a SOCKET_RECEIVED event in its event stack.
+    ///
+    /// @return always true.
+    virtual bool isSocketReceivedTimeSupported() const;
 
-    /// @brief returns socket bound to a specific address (or NULL)
+    /// @brief Simulate opening of the socket.
     ///
-    /// A helper function, used to pick a socketinfo that is bound to a given
-    /// address.
+    /// This function simulates opening a primary socket. In reality, it doesn't
+    /// open a socket but uses a pipe which can control if a read event is ready
+    /// or not.
     ///
-    /// @param sockets sockets collection
-    /// @param addr address the socket is bound to
+    /// @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).
     ///
-    /// @return socket info structure (or NULL)
-    const isc::dhcp::SocketInfo*
-    getSocketByAddr(const isc::dhcp::Iface::SocketCollection& sockets,
-                    const IOAddress& addr) {
-        for (auto const& s : sockets) {
-            if (s.addr_ == addr) {
-                return (&s);
-            }
-        }
-        return (NULL);
-    }
+    /// @return A SocketInfo structure with the socket descriptor set. The
+    /// fallback socket descriptor is set to a negative value.
+    virtual SocketInfo openSocket(Iface& iface,
+                                  const isc::asiolink::IOAddress& addr,
+                                  const uint16_t port,
+                                  const bool receive_bcast,
+                                  const bool send_bcast);
 
-    /// @brief Implements an IfaceMgr error handler.
+    /// @brief Simulate reception of the DHCPv4 message.
     ///
-    /// 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 iface An interface to be used to receive DHCPv4 message.
+    /// @param sock_info A descriptor of the primary and fallback sockets.
     ///
-    /// @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 may check if the expected number of errors
-        // has occurred.
-        ++errors_count_;
-    }
+    /// @return the same packet used by @ref send (if any).
+    virtual Pkt4Ptr receive(Iface& iface, const SocketInfo& sock_info);
 
-    /// @brief Tests the ability to send and receive DHCPv6 packets
+    /// @brief Simulates sending a DHCPv4 message.
     ///
-    /// This test calls @r IfaceMgr::configureDHCPPacketQueue, passing in the
-    /// given queue configuration.  It then calls IfaceMgr::startDHCPReceiver
-    /// and verifies whether or not the receive thread has been started as
-    /// expected.  Next it creates a generic DHCPv6 packet and sends it over
-    /// the loop back interface.  It invokes IfaceMgr::receive6 to receive the
-    /// packet sent, and compares to the packets for equality.
+    /// @param iface An interface to be used to send DHCPv4 message.
+    /// @param sockfd socket descriptor.
+    /// @param pkt A DHCPv4 to be sent.
     ///
-    /// @param dhcp_queue_control dhcp-queue-control contents to use for the test
-    /// @param exp_queue_enabled flag that indicates if packet queuing is expected
-    /// to be enabled.
-    void sendReceive6Test(data::ConstElementPtr dhcp_queue_control, bool exp_queue_enabled) {
-        scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+    /// @return 0.
+    virtual int send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt);
+};
 
-        // Testing socket operation in a portable way is tricky
-        // without interface detection implemented
-        // let's assume that every supported OS have lo interface
-        IOAddress lo_addr("::1");
-        int socket1 = 0, socket2 = 0;
-        EXPECT_NO_THROW(
-            socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10547);
-            socket2 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10546);
-        );
+class PktFilter6IfaceSocketTest : public PktFilterIfaceSocketTest, public PktFilter6 {
+public:
 
-        EXPECT_GE(socket1, 0);
-        EXPECT_GE(socket2, 0);
+    /// @brief Constructor.
+    ///
+    /// @param ready_on_send Flag which indicates if socket should be marked as
+    /// readReady when calling @ref send.
+    /// @param clear_on_read Flag which indicates is socket should be unmarked as
+    /// readReady when calling @ref receive.
+    PktFilter6IfaceSocketTest(bool ready_on_send = true, bool clear_on_read = true);
 
-        // Configure packet queueing as desired.
-        bool queue_enabled = false;
-        ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, dhcp_queue_control));
+    /// @brief Destructor.
+    ~PktFilter6IfaceSocketTest() = default;
 
-        // Verify that we have a queue only if we expected one.
-        ASSERT_EQ(exp_queue_enabled, queue_enabled);
+    /// @brief Simulate opening of the socket.
+    ///
+    /// This function simulates opening a primary socket. In reality, it doesn't
+    /// open a socket but uses a pipe which can control if a read event is ready
+    /// or not.
+    ///
+    /// @param iface Interface descriptor.
+    /// @param addr Address on the interface to be used to send packets.
+    /// @param port Port number.
+    /// @param join_multicast A boolean parameter which indicates whether
+    /// socket should join All_DHCP_Relay_Agents_and_servers multicast
+    /// group.
+    ///
+    /// @return A SocketInfo structure with the socket descriptor set. 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 join_multicast);
 
-        // Thread should only start when there is a packet queue.
-        ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6));
-        ASSERT_TRUE(queue_enabled == ifacemgr->isDHCPReceiverRunning());
+    /// @brief Simulate reception of the DHCPv6 message.
+    ///
+    /// @param iface An interface to be used to receive DHCPv6 message.
+    /// @param sock_info A descriptor of the primary and fallback sockets.
+    ///
+    /// @return the same packet used by @ref send (if any).
+    virtual Pkt6Ptr receive(const SocketInfo& sock_info);
 
-        // If the thread is already running, trying to start it again should fail.
-        if (queue_enabled) {
-            ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET6), InvalidOperation);
-            // Should still have one running.
-            ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
-        }
+    /// @brief Simulates sending a DHCPv6 message.
+    ///
+    /// @param iface An interface to be used to send DHCPv6 message.
+    /// @param sockfd socket descriptor.
+    /// @param pkt A DHCPv6 to be sent.
+    ///
+    /// @return 0.
+    virtual int send(const Iface& iface, uint16_t sockfd, const Pkt6Ptr& pkt);
+};
 
-        // Let's build our DHCPv6 packet.
-        // prepare dummy payload
-        uint8_t data[128];
-        for (uint8_t i = 0; i < 128; i++) {
-            data[i] = i;
-        }
+const uint8_t MARKER = 0;
 
-        Pkt6Ptr sendPkt = Pkt6Ptr(new Pkt6(data, 128));
-        sendPkt->repack();
-        sendPkt->setRemotePort(10547);
-        sendPkt->setRemoteAddr(IOAddress("::1"));
-        sendPkt->setIndex(LOOPBACK_INDEX);
-        sendPkt->setIface(LOOPBACK_NAME);
+PktFilterIfaceSocketTest::PktFilterIfaceSocketTest(bool ready_on_send, bool clear_on_read)
+    : ready_on_send_(ready_on_send),
+      clear_on_read_(clear_on_read) {
+}
 
-        // Send the packet.
-        EXPECT_EQ(true, ifacemgr->send(sendPkt));
+PktFilterIfaceSocketTest::~PktFilterIfaceSocketTest() {
+    for (auto it = socket_fds_.begin(); it != socket_fds_.end(); ++it) {
+        close(it->first);
+        close(it->second);
+    }
+}
 
-        // Now, let's try and receive it.
-        Pkt6Ptr rcvPkt;
-        rcvPkt = ifacemgr->receive6(10);
-        ASSERT_TRUE(rcvPkt); // received our own packet
+SocketInfo
+PktFilterIfaceSocketTest::openSocketCommon(const Iface& /* iface */, const isc::asiolink::IOAddress& addr,
+                                           const uint16_t) {
+    int pipe_fds[2];
+    if (pipe(pipe_fds) < 0) {
+        isc_throw(Unexpected, "failed to open test pipe");
+    }
+    if (fcntl(pipe_fds[0], F_SETFL, O_NONBLOCK) < 0) {
+        close(pipe_fds[0]);
+        close(pipe_fds[1]);
+        isc_throw(Unexpected, "fcntl " << strerror(errno));
+    }
+    if (fcntl(pipe_fds[1], F_SETFL, O_NONBLOCK) < 0) {
+        close(pipe_fds[0]);
+        close(pipe_fds[1]);
+        isc_throw(Unexpected, "fcntl " << strerror(errno));
+    }
 
-        // let's check that we received what was sent
-        ASSERT_EQ(sendPkt->data_.size(), rcvPkt->data_.size());
-        EXPECT_EQ(0, memcmp(&sendPkt->data_[0], &rcvPkt->data_[0],
-                            rcvPkt->data_.size()));
+    socket_fds_[pipe_fds[0]] = pipe_fds[1];
+    return (SocketInfo(addr, 9999, pipe_fds[0]));
+}
 
-        EXPECT_EQ(sendPkt->getRemoteAddr(), rcvPkt->getRemoteAddr());
+PktPtr
+PktFilterIfaceSocketTest::receiveCommon(const SocketInfo& s) {
+    auto it = socket_fds_.find(s.sockfd_);
+    if (it == socket_fds_.end()) {
+        std::cout << "receive no such socket: " << s.sockfd_ << std::endl;
+        return (PktPtr());
+    }
+    PktPtr result = pkts_[s.sockfd_];
+    if (clear_on_read_) {
+        uint8_t data;
+        for (size_t count = -1; count; count = read(s.sockfd_, &data, sizeof(data)));
+    }
+    return (result);
+}
 
-        // since we opened 2 sockets on the same interface and none of them is multicast,
-        // none is preferred over the other for sending data, so we really should not
-        // assume the one or the other will always be chosen for sending data. Therefore
-        // we should accept both values as source ports.
-        EXPECT_TRUE((rcvPkt->getRemotePort() == 10546) || (rcvPkt->getRemotePort() == 10547));
+int
+PktFilterIfaceSocketTest::sendCommon(const Iface& /* iface */, uint16_t sockfd, const PktPtr& pkt) {
+    auto it = socket_fds_.find(sockfd);
+    if (it == socket_fds_.end()) {
+        std::cout << "send no such socket: " << sockfd << std::endl;
+        return (-1);
+    }
+    pkts_[sockfd] = pkt;
+    if (ready_on_send_) {
+        uint8_t data = MARKER;
+        write(it->second, &data, sizeof(data));
+    }
+    return (0);
+}
 
-        // Close the socket. Further we will test if errors are reported
-        // properly on attempt to use closed socket.
-        close(socket2);
-
-        // @todo Closing the socket does NOT cause a read error out of the
-        // receiveDHCP<X>Packets() select.  Apparently this is because the
-        // thread is already inside the select when the socket is closed,
-        // and (at least under Centos 7.5), this does not interrupt the
-        // select.  For now, we'll only test this for direct receive.
-        if (!queue_enabled) {
-            EXPECT_THROW(ifacemgr->receive6(10), SocketFDError);
-        }
-
-        // Verify write fails.
-        EXPECT_THROW(ifacemgr->send(sendPkt), SocketWriteError);
-
-        // Stop the thread.  This should be no harm/no foul if we're not
-        // queueuing.  Either way, we should not have a thread afterwards.
-        ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver());
-        ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
-    }
-
-    /// @brief Tests the ability to send and receive DHCPv4 packets
-    ///
-    /// This test calls @r IfaceMgr::configureDHCPPacketQueue, passing in the
-    /// given queue configuration.  It then calls IfaceMgr::startDHCPReceiver
-    /// and verifies whether or not the receive thread has been started as
-    /// expected.  Next it creates a DISCOVER packet and sends it over
-    /// the loop back interface.  It invokes IfaceMgr::receive4 to receive the
-    /// packet sent, and compares to the packets for equality.
-    ///
-    /// @param dhcp_queue_control dhcp-queue-control contents to use for the test
-    /// @param exp_queue_enabled flag that indicates if packet queuing is expected
-    /// to be enabled.
-    void sendReceive4Test(data::ConstElementPtr dhcp_queue_control, bool exp_queue_enabled) {
-        scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
-
-        // Testing socket operation in a portable way is tricky
-        // without interface detection implemented.
-        // Let's assume that every supported OS has lo interface
-        IOAddress lo_addr("127.0.0.1");
-        int socket1 = 0;
-        EXPECT_NO_THROW(
-            socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr,
-                                           DHCP4_SERVER_PORT + 10000);
-        );
-
-        EXPECT_GE(socket1, 0);
-
-        // Configure packet queueing as desired.
-        bool queue_enabled = false;
-        ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, dhcp_queue_control));
-
-        // Verify that we have a queue only if we expected one.
-        ASSERT_EQ(exp_queue_enabled, queue_enabled);
+PktFilter4IfaceSocketTest::PktFilter4IfaceSocketTest(bool ready_on_send, bool clear_on_read)
+    : PktFilterIfaceSocketTest(ready_on_send, clear_on_read) {
+}
 
-        // Thread should only start when there is a packet queue.
-        ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
-        ASSERT_TRUE(queue_enabled == ifacemgr->isDHCPReceiverRunning());
+bool
+PktFilter4IfaceSocketTest::isDirectResponseSupported() const {
+    return (true);
+}
 
-        // If the thread is already running, trying to start it again should fail.
-        if (queue_enabled) {
-            ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET), InvalidOperation);
-            // Should still have one running.
-            ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
-        }
+bool
+PktFilter4IfaceSocketTest::isSocketReceivedTimeSupported() const {
+    return (true);
+}
 
-        // Let's construct the packet to send.
-        boost::shared_ptr<Pkt4> sendPkt(new Pkt4(DHCPDISCOVER, 1234) );
-        sendPkt->setLocalAddr(IOAddress("127.0.0.1"));
-        sendPkt->setLocalPort(DHCP4_SERVER_PORT + 10000 + 1);
-        sendPkt->setRemotePort(DHCP4_SERVER_PORT + 10000);
-        sendPkt->setRemoteAddr(IOAddress("127.0.0.1"));
-        sendPkt->setIndex(LOOPBACK_INDEX);
-        sendPkt->setIface(string(LOOPBACK_NAME));
-        sendPkt->setHops(6);
-        sendPkt->setSecs(42);
-        sendPkt->setCiaddr(IOAddress("192.0.2.1"));
-        sendPkt->setSiaddr(IOAddress("192.0.2.2"));
-        sendPkt->setYiaddr(IOAddress("192.0.2.3"));
-        sendPkt->setGiaddr(IOAddress("192.0.2.4"));
+SocketInfo
+PktFilter4IfaceSocketTest::openSocket(Iface& iface,
+                                      const isc::asiolink::IOAddress& addr,
+                                      const uint16_t port, const bool, const bool) {
+    return (PktFilterIfaceSocketTest::openSocketCommon(iface, addr, port));
+}
 
-        // Unpack() now checks if mandatory DHCP_MESSAGE_TYPE is present.
-        // Workarounds (creating DHCP Message Type Option by hand) are no longer
-        // needed as setDhcpType() is called in constructor.
+Pkt4Ptr
+PktFilter4IfaceSocketTest::receive(Iface& /* iface */, const SocketInfo& s) {
+    return (boost::dynamic_pointer_cast<Pkt4>(PktFilterIfaceSocketTest::receiveCommon(s)));
+}
 
-        uint8_t sname[] = "That's just a string that will act as SNAME";
-        sendPkt->setSname(sname, strlen((const char*)sname));
-        uint8_t file[] = "/another/string/that/acts/as/a/file_name.txt";
-        sendPkt->setFile(file, strlen((const char*)file));
+int
+PktFilter4IfaceSocketTest::send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt) {
+    return (PktFilterIfaceSocketTest::sendCommon(iface, sockfd, pkt));
+}
 
-        ASSERT_NO_THROW(
-            sendPkt->pack();
-        );
+PktFilter6IfaceSocketTest::PktFilter6IfaceSocketTest(bool ready_on_send, bool clear_on_read)
+    : PktFilterIfaceSocketTest(ready_on_send, clear_on_read) {
+}
 
-        // OK, Send the PACKET!
-        bool result = false;
-        EXPECT_NO_THROW(result = ifacemgr->send(sendPkt));
-        EXPECT_TRUE(result);
+SocketInfo
+PktFilter6IfaceSocketTest::openSocket(const Iface& iface,
+                                      const isc::asiolink::IOAddress& addr,
+                                      const uint16_t port, const bool) {
+    return (PktFilterIfaceSocketTest::openSocketCommon(iface, addr, port));
+}
 
-        // Now let's try and receive it.
-        boost::shared_ptr<Pkt4> rcvPkt;
-        ASSERT_NO_THROW(rcvPkt = ifacemgr->receive4(10));
-        ASSERT_TRUE(rcvPkt); // received our own packet
-        ASSERT_NO_THROW(
-            rcvPkt->unpack();
-        );
+Pkt6Ptr
+PktFilter6IfaceSocketTest::receive(const SocketInfo& s) {
+    return (boost::dynamic_pointer_cast<Pkt6>(PktFilterIfaceSocketTest::receiveCommon(s)));
+}
 
-        // let's check that we received what was sent
-        EXPECT_EQ(sendPkt->len(), rcvPkt->len());
-        EXPECT_EQ("127.0.0.1", rcvPkt->getRemoteAddr().toText());
-        EXPECT_EQ(sendPkt->getRemotePort(), rcvPkt->getLocalPort());
-        EXPECT_EQ(sendPkt->getHops(), rcvPkt->getHops());
-        EXPECT_EQ(sendPkt->getOp(),   rcvPkt->getOp());
-        EXPECT_EQ(sendPkt->getSecs(), rcvPkt->getSecs());
-        EXPECT_EQ(sendPkt->getFlags(), rcvPkt->getFlags());
-        EXPECT_EQ(sendPkt->getCiaddr(), rcvPkt->getCiaddr());
-        EXPECT_EQ(sendPkt->getSiaddr(), rcvPkt->getSiaddr());
-        EXPECT_EQ(sendPkt->getYiaddr(), rcvPkt->getYiaddr());
-        EXPECT_EQ(sendPkt->getGiaddr(), rcvPkt->getGiaddr());
-        EXPECT_EQ(sendPkt->getTransid(), rcvPkt->getTransid());
-        EXPECT_TRUE(sendPkt->getSname() == rcvPkt->getSname());
-        EXPECT_TRUE(sendPkt->getFile() == rcvPkt->getFile());
-        EXPECT_EQ(sendPkt->getHtype(), rcvPkt->getHtype());
-        EXPECT_EQ(sendPkt->getHlen(), rcvPkt->getHlen());
+int
+PktFilter6IfaceSocketTest::send(const Iface& iface, uint16_t sockfd, const Pkt6Ptr& pkt) {
+    return (PktFilterIfaceSocketTest::sendCommon(iface, sockfd, pkt));
+}
 
-        // since we opened 2 sockets on the same interface and none of them is multicast,
-        // none is preferred over the other for sending data, so we really should not
-        // assume the one or the other will always be chosen for sending data. We should
-        // skip checking source port of sent address.
+class NakedIfaceMgr: public IfaceMgr {
+    // "Naked" Interface Manager, exposes internal fields
+public:
 
-        // Close the socket. Further we will test if errors are reported
-        // properly on attempt to use closed socket.
-        close(socket1);
+    /// @brief Constructor.
+    NakedIfaceMgr() {
+        loDetect();
+    }
 
-        // @todo Closing the socket does NOT cause a read error out of the
-        // receiveDHCP<X>Packets() select.  Apparently this is because the
-        // thread is already inside the select when the socket is closed,
-        // and (at least under Centos 7.5), this does not interrupt the
-        // select.  For now, we'll only test this for direct receive.
-        if (!queue_enabled) {
-            EXPECT_THROW(ifacemgr->receive4(10), SocketFDError);
+    /// @brief detects name of the loopback interface
+    ///
+    /// This method detects name of the loopback interface.
+    static void loDetect() {
+        // Poor man's interface detection.  It will go away as soon as proper
+        // interface detection is implemented
+        if (if_nametoindex("lo") > 0) {
+            snprintf(LOOPBACK_NAME, BUF_SIZE - 1, "lo");
+        } else if (if_nametoindex("lo0") > 0) {
+            snprintf(LOOPBACK_NAME, BUF_SIZE - 1, "lo0");
+        } else {
+            cout << "Failed to detect loopback interface. Neither "
+                 << "lo nor lo0 worked. I give up." << endl;
+            FAIL();
         }
-
-        // Verify write fails.
-        EXPECT_THROW(ifacemgr->send(sendPkt), SocketWriteError);
-
-        // Stop the thread.  This should be no harm/no foul if we're not
-        // queueuing.  Either way, we should not have a thread afterwards.
-        ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver());
-        ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+        LOOPBACK_INDEX = if_nametoindex(LOOPBACK_NAME);
     }
 
-    /// @brief Verifies that IfaceMgr DHCPv4 receive calls detect and
-    /// ignore external sockets that have gone bad without affecting
-    /// affecting normal operations.  It can be run with or without
-    /// packet queuing.
+    /// @brief This function creates fictitious interfaces with fictitious
+    /// addresses.
     ///
-    /// @param use_queue determines if packet queuing is used or not.
-    void unusableExternalSockets4Test(bool use_queue = false) {
-        callback_ok = false;
-        callback2_ok = false;
-
-        scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
-
-        if (use_queue) {
-            bool queue_enabled = false;
-            data::ConstElementPtr config = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500);
-            ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, config));
-            ASSERT_TRUE(queue_enabled);
+    /// 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() {
 
-            // Thread should only start when there is a packet queue.
-            ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
-            ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
-        }
+        ifaces_.clear();
 
-        // Create first pipe and register it as extra socket
-        int pipefd[2];
-        EXPECT_TRUE(pipe(pipefd) == 0);
-        ASSERT_FALSE(ifacemgr->isExternalSocket(pipefd[0]));
-        EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0],
-                        [&pipefd](int fd) {
-                            callback_ok = (pipefd[0] == fd);
-                        }));
-        ASSERT_TRUE(ifacemgr->isExternalSocket(pipefd[0]));
-        ASSERT_FALSE(ifacemgr->isExternalSocketUnusable(pipefd[0]));
+        // local loopback
+        IfacePtr lo = createIface("lo", LO_INDEX);
+        lo->addAddress(IOAddress("127.0.0.1"));
+        lo->addAddress(IOAddress("::1"));
+        ifaces_.push_back(lo);
+        // eth0
+        IfacePtr eth0 = createIface("eth0", ETH0_INDEX);
+        eth0->addAddress(IOAddress("10.0.0.1"));
+        eth0->addAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"));
+        eth0->addAddress(IOAddress("2001:db8:1::1"));
+        ifaces_.push_back(eth0);
+        // eth1
+        IfacePtr eth1 = createIface("eth1", ETH1_INDEX);
+        eth1->addAddress(IOAddress("192.0.2.3"));
+        eth1->addAddress(IOAddress("fe80::3a60:77ff:fed5:abcd"));
+        ifaces_.push_back(eth1);
+    }
 
-        // Let's create a second pipe and register it as well
-        int secondpipe[2];
-        EXPECT_TRUE(pipe(secondpipe) == 0);
-        ASSERT_FALSE(ifacemgr->isExternalSocket(secondpipe[0]));
-        EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0],
-                        [&secondpipe](int fd) {
-                            callback2_ok = (secondpipe[0] == fd);
-                        }));
-        ASSERT_TRUE(ifacemgr->isExternalSocket(secondpipe[0]));
-        ASSERT_FALSE(ifacemgr->isExternalSocketUnusable(secondpipe[0]));
-
-        // Verify a call with no data and normal external sockets works ok.
-        Pkt4Ptr pkt4;
-        ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10)));
+    /// @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 IfacePtr createIface(const std::string& name, const unsigned int ifindex) {
+        IfacePtr iface(new Iface(name, ifindex));
+        if (name == "lo") {
+            iface->flag_loopback_ = true;
+            // Don't open sockets on loopback interface.
+            iface->inactive4_ = true;
+            iface->inactive6_ = true;
+        } else {
+            iface->inactive4_ = false;
+            iface->inactive6_ = false;
+        }
+        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;
+        return (iface);
+    }
 
-        // No callback invocations and no DHCPv4 pkt.
-        EXPECT_FALSE(callback_ok);
-        EXPECT_FALSE(callback2_ok);
-        EXPECT_FALSE(pkt4);
+    /// @brief Checks if the specified interface has a socket bound to a
+    /// specified address.
+    ///
+    /// @param iface_name A name of the interface.
+    /// @param addr An address to be checked for binding.
+    ///
+    /// @return true if there is a socket bound to the specified address.
+    bool isBound(const std::string& iface_name, const std::string& addr) {
+        IfacePtr iface = getIface(iface_name);
+        if (!iface) {
+            ADD_FAILURE() << "the interface " << iface_name << " doesn't exist";
+            return (false);
+        }
+        const Iface::SocketCollection& sockets = iface->getSockets();
+        for (auto const& sock : sockets) {
+            if (sock.addr_ == IOAddress(addr)) {
+                return (true);
 
-        // Now close the first pipe.  This should make it's external socket invalid.
-        close(pipefd[1]);
-        close(pipefd[0]);
+            } else if ((sock.addr_ == IOAddress("::")) &&
+                       (IOAddress(addr).isV6LinkLocal())) {
+                for (auto const& a : iface->getAddresses()) {
+                    if (a.get() == IOAddress(addr)) {
+                        return (true);
+                    }
+                }
+            }
+        }
+        return (false);
+    }
 
-        // We call receive4() which should detect and remove the invalid socket.
-        try {
-            pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10));
-        } catch (const SocketFDError& ex) {
-            std::ostringstream err_msg;
-            err_msg << "unexpected state (closed) for fd: " << pipefd[0];
-            EXPECT_EQ(err_msg.str(), ex.what());
-        } catch (const std::exception& ex) {
-            ADD_FAILURE() << "wrong exception thrown: " << ex.what();
+    /// @brief Modify 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 inactive4,
+                       const bool inactive6) {
+        for (auto const& iface : getIfaces()) {
+            if (iface->getName() == name) {
+                iface->flag_loopback_ = loopback;
+                iface->flag_up_ = up;
+                iface->flag_running_ = running;
+                iface->inactive4_ = inactive4;
+                iface->inactive6_ = inactive6;
+            }
         }
-        EXPECT_TRUE(ifacemgr->isExternalSocketUnusable(pipefd[0]));
-        EXPECT_FALSE(ifacemgr->isExternalSocketUnusable(secondpipe[0]));
+    }
+};
 
-        // No callback invocations and no DHCPv4 pkt.
-        EXPECT_FALSE(callback_ok);
-        EXPECT_FALSE(callback2_ok);
-        EXPECT_FALSE(pkt4);
+volatile bool callback_ok;
+volatile bool callback2_ok;
 
-        // Now check whether the second callback is still functional
-        EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38));
+void my_callback(int /* fd */) {
+    callback_ok = true;
+}
 
-        // Call receive4 again, this should work.
-        ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10)));
+void my_callback2(int /* fd */) {
+    callback2_ok = true;
+}
 
-        // Should have callback2 data only.
-        EXPECT_FALSE(callback_ok);
-        EXPECT_TRUE(callback2_ok);
-        EXPECT_FALSE(pkt4);
+/// @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:
 
-        // Stop the thread.  This should be no harm/no foul if we're not
-        // queueuing.  Either way, we should not have a thread afterwards.
-        ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver());
-        ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+    /// @brief Structure used to store and restore the system limit of open files.
+    struct rlimit limit_;
+
+    /// @brief Constructor.
+    IfaceMgrTest()
+        : errors_count_(0), kea_event_handler_type_("KEA_EVENT_HANDLER_TYPE") {
+        struct rlimit limit;
+        getrlimit(RLIMIT_NOFILE, &limit_);
+        limit.rlim_cur = 16 * FD_SETSIZE;
+        limit.rlim_max = 16 * FD_SETSIZE;
+        setrlimit(RLIMIT_NOFILE, &limit);
     }
 
-    /// @brief Verifies that IfaceMgr DHCPv6 receive calls detect and
-    /// ignore external sockets that have gone bad without affecting
-    /// affecting normal operations.  It can be run with or without
-    /// packet queuing.
+    ~IfaceMgrTest() {
+        setrlimit(RLIMIT_NOFILE, &limit_);
+        IfaceMgr::instance().stopDHCPReceiver();
+        IfaceMgr::instance().clearIfaces();
+        IfaceMgr::instance().deleteAllExternalSockets();
+        IfaceMgr::instance().detectIfaces();
+        IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet()));
+        IfaceMgr::instance().setPacketFilter(PktFilter6Ptr(new PktFilterInet6()));
+        IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, data::ConstElementPtr());
+        IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, data::ConstElementPtr());
+    }
+
+    /// @brief Tests the number of IPv6 sockets on interface
     ///
-    /// @param use_queue determines if packet queuing is used or not.
-    void unusableExternalSockets6Test(bool use_queue = false) {
-        callback_ok = false;
-        callback2_ok = false;
+    /// This function checks the expected number of open IPv6 sockets on the
+    /// specified interface. On non-Linux systems, sockets are bound to a
+    /// link-local address and the number of unicast addresses specified.
+    /// On Linux systems, there is two more sockets bound to ff02::1:2
+    /// and ff05::1:3 multicast addresses.
+    ///
+    /// @param iface An interface on which sockets are open.
+    /// @param unicast_num A number of unicast addresses bound.
+    /// @param link_local_num A number of link local addresses bound.
+    void checkSocketsCount6(const Iface& iface,
+                            const int unicast_num,
+                            const int link_local_num = 1) {
+        // On local-loopback interface, there should be no sockets.
+        if (iface.flag_loopback_) {
+            ASSERT_TRUE(iface.getSockets().empty())
+                << "expected empty socket set on loopback interface "
+                << iface.getName();
+            return;
+        }
+#if defined OS_LINUX
+        // On Linux, for each link-local address there may be two
+        // additional sockets opened and bound to multicast. These sockets
+        // are only opened if the interface is multicast-capable.
+        ASSERT_EQ(unicast_num
+                  + (iface.flag_multicast_ ? 2 * link_local_num : 0)
+                  + link_local_num,
+                  iface.getSockets().size())
+            << "invalid number of sockets on interface "
+            << iface.getName();
+#else
+        // On non-Linux, there is no additional socket.
+        ASSERT_EQ(unicast_num + link_local_num, iface.getSockets().size())
+            << "invalid number of sockets on interface "
+            << iface.getName();
 
-        scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+#endif
+    }
 
-        if (use_queue) {
-            bool queue_enabled = false;
-            data::ConstElementPtr config = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500);
-            ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, config));
-            ASSERT_TRUE(queue_enabled);
+    // Get the number of IPv4 or IPv6 sockets on the loopback interface
+    int getOpenSocketsCount(const Iface& iface, uint16_t family) const {
+        // Get all sockets.
+        Iface::SocketCollection sockets = iface.getSockets();
 
-            // Thread should only start when there is a packet queue.
-            ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6));
-            ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
+        // Loop through sockets and try to find the ones which match the
+        // specified type.
+        int sockets_count = 0;
+        for (auto const& sock : sockets) {
+            // Match found, increase the counter.
+            if (sock.family_ == family) {
+                ++sockets_count;
+            }
         }
+        return (sockets_count);
+    }
 
-        // Create first pipe and register it as extra socket
-        int pipefd[2];
-        EXPECT_TRUE(pipe(pipefd) == 0);
-        ASSERT_FALSE(ifacemgr->isExternalSocket(pipefd[0]));
-        EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0],
-                        [&pipefd](int fd) {
-                            callback_ok = (pipefd[0] == fd);
-                        }));
-        ASSERT_TRUE(ifacemgr->isExternalSocket(pipefd[0]));
-        ASSERT_FALSE(ifacemgr->isExternalSocketUnusable(pipefd[0]));
-
-        // Let's create a second pipe and register it as well
-        int secondpipe[2];
-        EXPECT_TRUE(pipe(secondpipe) == 0);
-        ASSERT_FALSE(ifacemgr->isExternalSocket(secondpipe[0]));
-        EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0],
-                        [&secondpipe](int fd) {
-                            callback2_ok = (secondpipe[0] == fd);
-                        }));
-        ASSERT_TRUE(ifacemgr->isExternalSocket(secondpipe[0]));
-        ASSERT_FALSE(ifacemgr->isExternalSocketUnusable(secondpipe[0]));
+    /// @brief returns socket bound to a specific address (or NULL)
+    ///
+    /// A helper function, used to pick a socketinfo that is bound to a given
+    /// address.
+    ///
+    /// @param sockets sockets collection
+    /// @param addr address the socket is bound to
+    ///
+    /// @return socket info structure (or NULL)
+    const isc::dhcp::SocketInfo*
+    getSocketByAddr(const isc::dhcp::Iface::SocketCollection& sockets,
+                    const IOAddress& addr) {
+        for (auto const& s : sockets) {
+            if (s.addr_ == addr) {
+                return (&s);
+            }
+        }
+        return (NULL);
+    }
 
-        // Verify a call with no data and normal external sockets works ok.
-        Pkt6Ptr pkt6;
-        ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10)));
+    /// @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 may check if the expected number of errors
+        // has occurred.
+        ++errors_count_;
+    }
 
-        // No callback invocations and no DHCPv6 pkt.
-        EXPECT_FALSE(callback_ok);
-        EXPECT_FALSE(callback2_ok);
-        EXPECT_FALSE(pkt6);
+    /// @brief Tests the ability to send and receive DHCPv6 packets
+    ///
+    /// This test calls @r IfaceMgr::configureDHCPPacketQueue, passing in the
+    /// given queue configuration.  It then calls IfaceMgr::startDHCPReceiver
+    /// and verifies whether or not the receive thread has been started as
+    /// expected.  Next it creates a generic DHCPv6 packet and sends it over
+    /// the loop back interface.  It invokes IfaceMgr::receive6 to receive the
+    /// packet sent, and compares to the packets for equality.
+    ///
+    /// @param dhcp_queue_control dhcp-queue-control contents to use for the test
+    /// @param exp_queue_enabled flag that indicates if packet queuing is expected
+    /// to be enabled.
+    void sendReceive6Test(data::ConstElementPtr dhcp_queue_control, bool exp_queue_enabled) {
+        scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
 
-        // Now close the first pipe.  This should make it's external socket invalid.
-        close(pipefd[1]);
-        close(pipefd[0]);
+        // Testing socket operation in a portable way is tricky
+        // without interface detection implemented
+        // let's assume that every supported OS have lo interface
+        IOAddress lo_addr("::1");
+        int socket1 = 0, socket2 = 0;
+        EXPECT_NO_THROW(
+            socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10547);
+            socket2 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr, 10546);
+        );
 
-        // We call receive6() which should detect and remove the invalid socket.
-        try {
-            pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10));
-        } catch (const SocketFDError& ex) {
-            std::ostringstream err_msg;
-            err_msg << "unexpected state (closed) for fd: " << pipefd[0];
-            EXPECT_EQ(err_msg.str(), ex.what());
-        } catch (const std::exception& ex) {
-            ADD_FAILURE() << "wrong exception thrown: " << ex.what();
+        EXPECT_GE(socket1, 0);
+        EXPECT_GE(socket2, 0);
+
+        // Configure packet queueing as desired.
+        bool queue_enabled = false;
+        ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, dhcp_queue_control));
+
+        // Verify that we have a queue only if we expected one.
+        ASSERT_EQ(exp_queue_enabled, queue_enabled);
+
+        // Thread should only start when there is a packet queue.
+        ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6));
+        ASSERT_TRUE(queue_enabled == ifacemgr->isDHCPReceiverRunning());
+
+        // If the thread is already running, trying to start it again should fail.
+        if (queue_enabled) {
+            ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET6), InvalidOperation);
+            // Should still have one running.
+            ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
         }
-        EXPECT_TRUE(ifacemgr->isExternalSocketUnusable(pipefd[0]));
-        EXPECT_FALSE(ifacemgr->isExternalSocketUnusable(secondpipe[0]));
 
-        // No callback invocations and no DHCPv6 pkt.
-        EXPECT_FALSE(callback_ok);
-        EXPECT_FALSE(callback2_ok);
-        EXPECT_FALSE(pkt6);
+        // Let's build our DHCPv6 packet.
+        // prepare dummy payload
+        uint8_t data[128];
+        for (uint8_t i = 0; i < 128; i++) {
+            data[i] = i;
+        }
 
-        // Now check whether the second callback is still functional
-        EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38));
+        Pkt6Ptr sendPkt = Pkt6Ptr(new Pkt6(data, 128));
+        sendPkt->repack();
+        sendPkt->setRemotePort(10547);
+        sendPkt->setRemoteAddr(IOAddress("::1"));
+        sendPkt->setIndex(LOOPBACK_INDEX);
+        sendPkt->setIface(LOOPBACK_NAME);
 
-        // Call receive6 again, this should work.
-        ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10)));
+        // Send the packet.
+        EXPECT_EQ(true, ifacemgr->send(sendPkt));
 
-        // Should have callback2 data only.
-        EXPECT_FALSE(callback_ok);
-        EXPECT_TRUE(callback2_ok);
-        EXPECT_FALSE(pkt6);
+        // Now, let's try and receive it.
+        Pkt6Ptr rcvPkt;
+        rcvPkt = ifacemgr->receive6(10);
+        ASSERT_TRUE(rcvPkt); // received our own packet
+
+        // let's check that we received what was sent
+        ASSERT_EQ(sendPkt->data_.size(), rcvPkt->data_.size());
+        EXPECT_EQ(0, memcmp(&sendPkt->data_[0], &rcvPkt->data_[0],
+                            rcvPkt->data_.size()));
+
+        EXPECT_EQ(sendPkt->getRemoteAddr(), rcvPkt->getRemoteAddr());
+
+        // since we opened 2 sockets on the same interface and none of them is multicast,
+        // none is preferred over the other for sending data, so we really should not
+        // assume the one or the other will always be chosen for sending data. Therefore
+        // we should accept both values as source ports.
+        EXPECT_TRUE((rcvPkt->getRemotePort() == 10546) || (rcvPkt->getRemotePort() == 10547));
+
+        // Close the socket. Further we will test if errors are reported
+        // properly on attempt to use closed socket.
+        close(socket2);
+
+        // @todo Closing the socket does NOT cause a read error out of the
+        // receiveDHCP<X>Packets() select.  Apparently this is because the
+        // thread is already inside the select when the socket is closed,
+        // and (at least under Centos 7.5), this does not interrupt the
+        // select.  For now, we'll only test this for direct receive.
+        if (!queue_enabled) {
+            EXPECT_THROW(ifacemgr->receive6(10), SocketFDError);
+        }
+
+        // Verify write fails.
+        EXPECT_THROW(ifacemgr->send(sendPkt), SocketWriteError);
 
         // Stop the thread.  This should be no harm/no foul if we're not
         // queueuing.  Either way, we should not have a thread afterwards.
@@ -899,188 +907,418 @@ public:
         ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
     }
 
-    /// @brief test receive timeout (v6).
-    void testReceiveTimeout6();
+    /// @brief Tests the ability to send and receive DHCPv4 packets
+    ///
+    /// This test calls @r IfaceMgr::configureDHCPPacketQueue, passing in the
+    /// given queue configuration.  It then calls IfaceMgr::startDHCPReceiver
+    /// and verifies whether or not the receive thread has been started as
+    /// expected.  Next it creates a DISCOVER packet and sends it over
+    /// the loop back interface.  It invokes IfaceMgr::receive4 to receive the
+    /// packet sent, and compares to the packets for equality.
+    ///
+    /// @param dhcp_queue_control dhcp-queue-control contents to use for the test
+    /// @param exp_queue_enabled flag that indicates if packet queuing is expected
+    /// to be enabled.
+    void sendReceive4Test(data::ConstElementPtr dhcp_queue_control, bool exp_queue_enabled) {
+        scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
 
-    /// @brief test receive timeout (v4).
-    void testReceiveTimeout4();
+        // Testing socket operation in a portable way is tricky
+        // without interface detection implemented.
+        // Let's assume that every supported OS has lo interface
+        IOAddress lo_addr("127.0.0.1");
+        int socket1 = 0;
+        EXPECT_NO_THROW(
+            socket1 = ifacemgr->openSocket(LOOPBACK_NAME, lo_addr,
+                                           DHCP4_SERVER_PORT + 10000);
+        );
 
-    /// @brief Tests single external socket (v4).
-    void testSingleExternalSocket4();
+        EXPECT_GE(socket1, 0);
 
-    /// @brief Tests multiple external sockets (v4);
-    void testMultipleExternalSockets4();
+        // Configure packet queueing as desired.
+        bool queue_enabled = false;
+        ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, dhcp_queue_control));
 
-    /// @brief Tests if existing external socket can be deleted (v4).
-    void testDeleteExternalSockets4();
+        // Verify that we have a queue only if we expected one.
+        ASSERT_EQ(exp_queue_enabled, queue_enabled);
 
-    /// @brief Tests single external socket (v6).
-    void testSingleExternalSocket6();
+        // Thread should only start when there is a packet queue.
+        ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
+        ASSERT_TRUE(queue_enabled == ifacemgr->isDHCPReceiverRunning());
 
-    /// @brief Tests multiple external sockets (v6);
-    void testMultipleExternalSockets6();
+        // If the thread is already running, trying to start it again should fail.
+        if (queue_enabled) {
+            ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET), InvalidOperation);
+            // Should still have one running.
+            ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
+        }
 
-    /// @brief Tests if existing external socket can be deleted (v6).
-    void testDeleteExternalSockets6();
+        // Let's construct the packet to send.
+        boost::shared_ptr<Pkt4> sendPkt(new Pkt4(DHCPDISCOVER, 1234) );
+        sendPkt->setLocalAddr(IOAddress("127.0.0.1"));
+        sendPkt->setLocalPort(DHCP4_SERVER_PORT + 10000 + 1);
+        sendPkt->setRemotePort(DHCP4_SERVER_PORT + 10000);
+        sendPkt->setRemoteAddr(IOAddress("127.0.0.1"));
+        sendPkt->setIndex(LOOPBACK_INDEX);
+        sendPkt->setIface(string(LOOPBACK_NAME));
+        sendPkt->setHops(6);
+        sendPkt->setSecs(42);
+        sendPkt->setCiaddr(IOAddress("192.0.2.1"));
+        sendPkt->setSiaddr(IOAddress("192.0.2.2"));
+        sendPkt->setYiaddr(IOAddress("192.0.2.3"));
+        sendPkt->setGiaddr(IOAddress("192.0.2.4"));
 
-    /// @brief Verifies that IfaceMgr DHCPv4 receive calls follow
-    /// a LRU order.
-    void lruExternalSockets4Test() {
-        bool callback1_ok = false;
-        bool callback2_ok = false;
+        // Unpack() now checks if mandatory DHCP_MESSAGE_TYPE is present.
+        // Workarounds (creating DHCP Message Type Option by hand) are no longer
+        // needed as setDhcpType() is called in constructor.
 
-        scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+        uint8_t sname[] = "That's just a string that will act as SNAME";
+        sendPkt->setSname(sname, strlen((const char*)sname));
+        uint8_t file[] = "/another/string/that/acts/as/a/file_name.txt";
+        sendPkt->setFile(file, strlen((const char*)file));
 
-        // Create 3 watch sockets.
-        WatchSocket ws0;
-        WatchSocket ws1;
-        WatchSocket ws2;
+        ASSERT_NO_THROW(
+            sendPkt->pack();
+        );
 
-        // Register them. the first with no handle.
-        EXPECT_NO_THROW(ifacemgr->addExternalSocket(ws0.getSelectFd(), 0));
-        auto handler1 = [&callback1_ok, &ws1](int) {
-            callback1_ok = true;
-            EXPECT_NO_THROW(ws1.clearReady());
-        };
-        EXPECT_NO_THROW(ifacemgr->addExternalSocket(ws1.getSelectFd(), handler1));
-        auto handler2 = [&callback2_ok, &ws2](int) {
-            callback2_ok = true;
-            EXPECT_NO_THROW(ws2.clearReady());
-        };
-        EXPECT_NO_THROW(ifacemgr->addExternalSocket(ws2.getSelectFd(), handler2));
+        // OK, Send the PACKET!
+        bool result = false;
+        EXPECT_NO_THROW(result = ifacemgr->send(sendPkt));
+        EXPECT_TRUE(result);
 
-        // Mark all watch sockets as ready.
-        ws0.markReady();
-        ws1.markReady();
-        ws2.markReady();
-        EXPECT_FALSE(callback1_ok);
-        EXPECT_FALSE(callback2_ok);
+        // Now let's try and receive it.
+        boost::shared_ptr<Pkt4> rcvPkt;
+        ASSERT_NO_THROW(rcvPkt = ifacemgr->receive4(10));
+        ASSERT_TRUE(rcvPkt); // received our own packet
+        ASSERT_NO_THROW(
+            rcvPkt->unpack();
+        );
 
-        /// Check the order before the first call to receive4.
-        std::list<int> expected;
-        expected.push_back(ws0.getSelectFd());
-        expected.push_back(ws1.getSelectFd());
-        expected.push_back(ws2.getSelectFd());
-        EXPECT_EQ(expected, ifacemgr->getAllExternalSockets());
+        // let's check that we received what was sent
+        EXPECT_EQ(sendPkt->len(), rcvPkt->len());
+        EXPECT_EQ("127.0.0.1", rcvPkt->getRemoteAddr().toText());
+        EXPECT_EQ(sendPkt->getRemotePort(), rcvPkt->getLocalPort());
+        EXPECT_EQ(sendPkt->getHops(), rcvPkt->getHops());
+        EXPECT_EQ(sendPkt->getOp(),   rcvPkt->getOp());
+        EXPECT_EQ(sendPkt->getSecs(), rcvPkt->getSecs());
+        EXPECT_EQ(sendPkt->getFlags(), rcvPkt->getFlags());
+        EXPECT_EQ(sendPkt->getCiaddr(), rcvPkt->getCiaddr());
+        EXPECT_EQ(sendPkt->getSiaddr(), rcvPkt->getSiaddr());
+        EXPECT_EQ(sendPkt->getYiaddr(), rcvPkt->getYiaddr());
+        EXPECT_EQ(sendPkt->getGiaddr(), rcvPkt->getGiaddr());
+        EXPECT_EQ(sendPkt->getTransid(), rcvPkt->getTransid());
+        EXPECT_TRUE(sendPkt->getSname() == rcvPkt->getSname());
+        EXPECT_TRUE(sendPkt->getFile() == rcvPkt->getFile());
+        EXPECT_EQ(sendPkt->getHtype(), rcvPkt->getHtype());
+        EXPECT_EQ(sendPkt->getHlen(), rcvPkt->getHlen());
 
-        // First call to receive4: ws0 and ws1 are scanned and moved.
+        // since we opened 2 sockets on the same interface and none of them is multicast,
+        // none is preferred over the other for sending data, so we really should not
+        // assume the one or the other will always be chosen for sending data. We should
+        // skip checking source port of sent address.
+
+        // Close the socket. Further we will test if errors are reported
+        // properly on attempt to use closed socket.
+        close(socket1);
+
+        // @todo Closing the socket does NOT cause a read error out of the
+        // receiveDHCP<X>Packets() select.  Apparently this is because the
+        // thread is already inside the select when the socket is closed,
+        // and (at least under Centos 7.5), this does not interrupt the
+        // select.  For now, we'll only test this for direct receive.
+        if (!queue_enabled) {
+            EXPECT_THROW(ifacemgr->receive4(10), SocketFDError);
+        }
+
+        // Verify write fails.
+        EXPECT_THROW(ifacemgr->send(sendPkt), SocketWriteError);
+
+        // Stop the thread.  This should be no harm/no foul if we're not
+        // queueuing.  Either way, we should not have a thread afterwards.
+        ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver());
+        ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+    }
+
+    /// @brief Verifies that IfaceMgr DHCPv4 receive calls detect and
+    /// ignore external sockets that have gone bad without affecting
+    /// affecting normal operations.  It can be run with or without
+    /// packet queuing.
+    ///
+    /// @param use_queue determines if packet queuing is used or not.
+    void unusableExternalSockets4Test(bool use_queue = false) {
+        callback_ok = false;
+        callback2_ok = false;
+
+        scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+        if (use_queue) {
+            bool queue_enabled = false;
+            data::ConstElementPtr config = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500);
+            ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, config));
+            ASSERT_TRUE(queue_enabled);
+
+            // Thread should only start when there is a packet queue.
+            ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
+            ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
+        }
+
+        // Create first pipe and register it as extra socket
+        int pipefd[2];
+        EXPECT_TRUE(pipe(pipefd) == 0);
+        ASSERT_FALSE(ifacemgr->isExternalSocket(pipefd[0]));
+        EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0],
+                        [&pipefd](int fd) {
+                            callback_ok = (pipefd[0] == fd);
+                        }));
+        ASSERT_TRUE(ifacemgr->isExternalSocket(pipefd[0]));
+        ASSERT_FALSE(ifacemgr->isExternalSocketUnusable(pipefd[0]));
+
+        // Let's create a second pipe and register it as well
+        int secondpipe[2];
+        EXPECT_TRUE(pipe(secondpipe) == 0);
+        ASSERT_FALSE(ifacemgr->isExternalSocket(secondpipe[0]));
+        EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0],
+                        [&secondpipe](int fd) {
+                            callback2_ok = (secondpipe[0] == fd);
+                        }));
+        ASSERT_TRUE(ifacemgr->isExternalSocket(secondpipe[0]));
+        ASSERT_FALSE(ifacemgr->isExternalSocketUnusable(secondpipe[0]));
+
+        // Verify a call with no data and normal external sockets works ok.
         Pkt4Ptr pkt4;
         ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10)));
-        EXPECT_TRUE(callback1_ok);
+
+        // No callback invocations and no DHCPv4 pkt.
+        EXPECT_FALSE(callback_ok);
         EXPECT_FALSE(callback2_ok);
         EXPECT_FALSE(pkt4);
-        expected.clear();
-        expected.push_back(ws2.getSelectFd());
-        expected.push_back(ws0.getSelectFd());
-        expected.push_back(ws1.getSelectFd());
-        EXPECT_EQ(expected, ifacemgr->getAllExternalSockets());
 
-        // Second call to receive4: ws2 is scanned and moved.
-        callback1_ok = false;
-        ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10)));
-        EXPECT_FALSE(callback1_ok);
-        EXPECT_TRUE(callback2_ok);
+        // Now close the first pipe.  This should make it's external socket invalid.
+        close(pipefd[1]);
+        close(pipefd[0]);
+
+        // We call receive4() which should detect and remove the invalid socket.
+        try {
+            pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10));
+        } catch (const SocketFDError& ex) {
+            std::ostringstream err_msg;
+            err_msg << "unexpected state (closed) for fd: " << pipefd[0];
+            EXPECT_EQ(err_msg.str(), ex.what());
+        } catch (const std::exception& ex) {
+            ADD_FAILURE() << "wrong exception thrown: " << ex.what();
+        }
+        EXPECT_TRUE(ifacemgr->isExternalSocketUnusable(pipefd[0]));
+        EXPECT_FALSE(ifacemgr->isExternalSocketUnusable(secondpipe[0]));
+
+        // No callback invocations and no DHCPv4 pkt.
+        EXPECT_FALSE(callback_ok);
+        EXPECT_FALSE(callback2_ok);
         EXPECT_FALSE(pkt4);
-        expected.clear();
-        expected.push_back(ws0.getSelectFd());
-        expected.push_back(ws1.getSelectFd());
-        expected.push_back(ws2.getSelectFd());
-        EXPECT_EQ(expected, ifacemgr->getAllExternalSockets());
 
-        // Third call to receive4: ws0 is scanned and moved.
-        callback1_ok = false;
-        callback2_ok = false;
+        // Now check whether the second callback is still functional
+        EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38));
+
+        // Call receive4 again, this should work.
         ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10)));
-        EXPECT_FALSE(callback1_ok);
-        EXPECT_FALSE(callback2_ok);
+
+        // Should have callback2 data only.
+        EXPECT_FALSE(callback_ok);
+        EXPECT_TRUE(callback2_ok);
         EXPECT_FALSE(pkt4);
-        expected.clear();
-        expected.push_back(ws1.getSelectFd());
-        expected.push_back(ws2.getSelectFd());
-        expected.push_back(ws0.getSelectFd());
-        EXPECT_EQ(expected, ifacemgr->getAllExternalSockets());
 
-        EXPECT_NO_THROW(ws0.clearReady());
-        std::string err;
-        EXPECT_NO_THROW(ws0.closeSocket(err));
-        EXPECT_EQ("", err);
-        EXPECT_NO_THROW(ws1.closeSocket(err));
-        EXPECT_EQ("", err);
-        EXPECT_NO_THROW(ws2.closeSocket(err));
-        EXPECT_EQ("", err);
+        // Stop the thread.  This should be no harm/no foul if we're not
+        // queueuing.  Either way, we should not have a thread afterwards.
+        ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver());
+        ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
     }
 
-    /// @brief Verifies that IfaceMgr DHCPv6 receive calls follow
-    /// a LRU order.
-    void lruExternalSockets6Test() {
-        bool callback1_ok = false;
-        bool callback2_ok = false;
+    /// @brief Verifies that IfaceMgr DHCPv6 receive calls detect and
+    /// ignore external sockets that have gone bad without affecting
+    /// affecting normal operations.  It can be run with or without
+    /// packet queuing.
+    ///
+    /// @param use_queue determines if packet queuing is used or not.
+    void unusableExternalSockets6Test(bool use_queue = false) {
+        callback_ok = false;
+        callback2_ok = false;
 
         scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
 
-        // Create 3 watch sockets.
-        WatchSocket ws0;
-        WatchSocket ws1;
-        WatchSocket ws2;
+        if (use_queue) {
+            bool queue_enabled = false;
+            data::ConstElementPtr config = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500);
+            ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, config));
+            ASSERT_TRUE(queue_enabled);
 
-        // Register them. the first with no handle.
-        EXPECT_NO_THROW(ifacemgr->addExternalSocket(ws0.getSelectFd(), 0));
-        auto handler1 = [&callback1_ok, &ws1](int) {
-            callback1_ok = true;
-            EXPECT_NO_THROW(ws1.clearReady());
-        };
-        EXPECT_NO_THROW(ifacemgr->addExternalSocket(ws1.getSelectFd(), handler1));
-        auto handler2 = [&callback2_ok, &ws2](int) {
-            callback2_ok = true;
-            EXPECT_NO_THROW(ws2.clearReady());
-        };
-        EXPECT_NO_THROW(ifacemgr->addExternalSocket(ws2.getSelectFd(), handler2));
+            // Thread should only start when there is a packet queue.
+            ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6));
+            ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
+        }
 
-        // Mark all watch sockets as ready.
-        ws0.markReady();
-        ws1.markReady();
-        ws2.markReady();
-        EXPECT_FALSE(callback1_ok);
-        EXPECT_FALSE(callback2_ok);
+        // Create first pipe and register it as extra socket
+        int pipefd[2];
+        EXPECT_TRUE(pipe(pipefd) == 0);
+        ASSERT_FALSE(ifacemgr->isExternalSocket(pipefd[0]));
+        EXPECT_NO_THROW(ifacemgr->addExternalSocket(pipefd[0],
+                        [&pipefd](int fd) {
+                            callback_ok = (pipefd[0] == fd);
+                        }));
+        ASSERT_TRUE(ifacemgr->isExternalSocket(pipefd[0]));
+        ASSERT_FALSE(ifacemgr->isExternalSocketUnusable(pipefd[0]));
 
-        /// Check the order before the first call to receive6.
-        std::list<int> expected;
-        expected.push_back(ws0.getSelectFd());
-        expected.push_back(ws1.getSelectFd());
-        expected.push_back(ws2.getSelectFd());
-        EXPECT_EQ(expected, ifacemgr->getAllExternalSockets());
+        // Let's create a second pipe and register it as well
+        int secondpipe[2];
+        EXPECT_TRUE(pipe(secondpipe) == 0);
+        ASSERT_FALSE(ifacemgr->isExternalSocket(secondpipe[0]));
+        EXPECT_NO_THROW(ifacemgr->addExternalSocket(secondpipe[0],
+                        [&secondpipe](int fd) {
+                            callback2_ok = (secondpipe[0] == fd);
+                        }));
+        ASSERT_TRUE(ifacemgr->isExternalSocket(secondpipe[0]));
+        ASSERT_FALSE(ifacemgr->isExternalSocketUnusable(secondpipe[0]));
 
-        // First call to receive6: ws0 and ws1 are scanned and moved.
+        // Verify a call with no data and normal external sockets works ok.
         Pkt6Ptr pkt6;
         ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10)));
-        EXPECT_TRUE(callback1_ok);
+
+        // No callback invocations and no DHCPv6 pkt.
+        EXPECT_FALSE(callback_ok);
         EXPECT_FALSE(callback2_ok);
         EXPECT_FALSE(pkt6);
-        expected.clear();
-        expected.push_back(ws2.getSelectFd());
-        expected.push_back(ws0.getSelectFd());
-        expected.push_back(ws1.getSelectFd());
-        EXPECT_EQ(expected, ifacemgr->getAllExternalSockets());
 
-        // Second call to receive6: ws2 is scanned and moved.
-        callback1_ok = false;
-        ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10)));
-        EXPECT_FALSE(callback1_ok);
-        EXPECT_TRUE(callback2_ok);
-        EXPECT_FALSE(pkt6);
-        expected.clear();
-        expected.push_back(ws0.getSelectFd());
-        expected.push_back(ws1.getSelectFd());
-        expected.push_back(ws2.getSelectFd());
+        // Now close the first pipe.  This should make it's external socket invalid.
+        close(pipefd[1]);
+        close(pipefd[0]);
+
+        // We call receive6() which should detect and remove the invalid socket.
+        try {
+            pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10));
+        } catch (const SocketFDError& ex) {
+            std::ostringstream err_msg;
+            err_msg << "unexpected state (closed) for fd: " << pipefd[0];
+            EXPECT_EQ(err_msg.str(), ex.what());
+        } catch (const std::exception& ex) {
+            ADD_FAILURE() << "wrong exception thrown: " << ex.what();
+        }
+        EXPECT_TRUE(ifacemgr->isExternalSocketUnusable(pipefd[0]));
+        EXPECT_FALSE(ifacemgr->isExternalSocketUnusable(secondpipe[0]));
+
+        // No callback invocations and no DHCPv6 pkt.
+        EXPECT_FALSE(callback_ok);
+        EXPECT_FALSE(callback2_ok);
+        EXPECT_FALSE(pkt6);
+
+        // Now check whether the second callback is still functional
+        EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38));
+
+        // Call receive6 again, this should work.
+        ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10)));
+
+        // Should have callback2 data only.
+        EXPECT_FALSE(callback_ok);
+        EXPECT_TRUE(callback2_ok);
+        EXPECT_FALSE(pkt6);
+
+        // Stop the thread.  This should be no harm/no foul if we're not
+        // queueuing.  Either way, we should not have a thread afterwards.
+        ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver());
+        ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+    }
+
+    /// @brief test receive timeout (v6).
+    void testReceiveTimeout6();
+
+    /// @brief test receive timeout (v4).
+    void testReceiveTimeout4();
+
+    /// @brief Tests single external socket (v4).
+    void testSingleExternalSocket4();
+
+    /// @brief Tests multiple external sockets (v4);
+    void testMultipleExternalSockets4();
+
+    /// @brief Tests if existing external socket can be deleted (v4).
+    void testDeleteExternalSockets4();
+
+    /// @brief Tests single external socket (v6).
+    void testSingleExternalSocket6();
+
+    /// @brief Tests multiple external sockets (v6);
+    void testMultipleExternalSockets6();
+
+    /// @brief Tests if existing external socket can be deleted (v6).
+    void testDeleteExternalSockets6();
+
+    /// @brief Verifies that IfaceMgr DHCPv4 receive calls follow
+    /// a LRU order.
+    void lruExternalSockets4Test() {
+        callback_ok = false;
+        callback2_ok = false;
+
+        scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+        // Create 3 watch sockets.
+        WatchSocket ws0;
+        WatchSocket ws1;
+        WatchSocket ws2;
+
+        // Register them. the first with no handle.
+        EXPECT_NO_THROW(ifacemgr->addExternalSocket(ws0.getSelectFd(), 0));
+        auto handler1 = [&ws1](int) {
+            callback_ok = true;
+            EXPECT_NO_THROW(ws1.clearReady());
+        };
+        EXPECT_NO_THROW(ifacemgr->addExternalSocket(ws1.getSelectFd(), handler1));
+        auto handler2 = [&ws2](int) {
+            callback2_ok = true;
+            EXPECT_NO_THROW(ws2.clearReady());
+        };
+        EXPECT_NO_THROW(ifacemgr->addExternalSocket(ws2.getSelectFd(), handler2));
+
+        // Mark all watch sockets as ready.
+        ws0.markReady();
+        ws1.markReady();
+        ws2.markReady();
+        EXPECT_FALSE(callback_ok);
+        EXPECT_FALSE(callback2_ok);
+
+        /// Check the order before the first call to receive4.
+        std::list<int> expected;
+        expected.push_back(ws0.getSelectFd());
+        expected.push_back(ws1.getSelectFd());
+        expected.push_back(ws2.getSelectFd());
         EXPECT_EQ(expected, ifacemgr->getAllExternalSockets());
 
-        // Third call to receive6: ws0 is scanned and moved.
-        callback1_ok = false;
+        // First call to receive4: ws0 and ws1 are scanned and moved.
+        Pkt4Ptr pkt4;
+        ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10)));
+        EXPECT_TRUE(callback_ok);
+        EXPECT_FALSE(callback2_ok);
+        EXPECT_FALSE(pkt4);
+        expected.clear();
+        expected.push_back(ws2.getSelectFd());
+        expected.push_back(ws0.getSelectFd());
+        expected.push_back(ws1.getSelectFd());
+        EXPECT_EQ(expected, ifacemgr->getAllExternalSockets());
+
+        // Second call to receive4: ws2 is scanned and moved.
+        callback_ok = false;
+        ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10)));
+        EXPECT_FALSE(callback_ok);
+        EXPECT_TRUE(callback2_ok);
+        EXPECT_FALSE(pkt4);
+        expected.clear();
+        expected.push_back(ws0.getSelectFd());
+        expected.push_back(ws1.getSelectFd());
+        expected.push_back(ws2.getSelectFd());
+        EXPECT_EQ(expected, ifacemgr->getAllExternalSockets());
+
+        // Third call to receive4: ws0 is scanned and moved.
+        callback_ok = false;
         callback2_ok = false;
-        ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10)));
-        EXPECT_FALSE(callback1_ok);
+        ASSERT_NO_THROW(pkt4 = ifacemgr->receive4(RECEIVE_WAIT_MS(10)));
+        EXPECT_FALSE(callback_ok);
         EXPECT_FALSE(callback2_ok);
-        EXPECT_FALSE(pkt6);
+        EXPECT_FALSE(pkt4);
         expected.clear();
         expected.push_back(ws1.getSelectFd());
         expected.push_back(ws2.getSelectFd());
@@ -1097,199 +1335,922 @@ public:
         EXPECT_EQ("", err);
     }
 
-    /// @brief Holds the invocation counter for ifaceMgrErrorHandler.
-    int errors_count_;
+    /// @brief Verifies that IfaceMgr DHCPv6 receive calls follow
+    /// a LRU order.
+    void lruExternalSockets6Test() {
+        callback_ok = false;
+        callback2_ok = false;
 
-    /// @brief RAII wrapper for KEA_EVENT_HANDLER_TYPE env variable.
-    EnvVarWrapper kea_event_handler_type_;
-};
+        scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
 
-// We need some known interface to work reliably. Loopback interface is named
-// lo on Linux and lo0 on BSD boxes. We need to find out which is available.
-// This is not a real test, but rather a workaround that will go away when
-// interface detection is implemented on all OSes.
-TEST_F(IfaceMgrTest, loDetect) {
-    NakedIfaceMgr::loDetect();
-}
+        // Create 3 watch sockets.
+        WatchSocket ws0;
+        WatchSocket ws1;
+        WatchSocket ws2;
 
-// Uncomment this test to create packet writer. It will
-// write incoming DHCPv6 packets as C arrays. That is useful
-// for generating test sequences based on actual traffic
-//
-// TODO: this potentially should be moved to a separate tool
-//
+        // Register them. the first with no handle.
+        EXPECT_NO_THROW(ifacemgr->addExternalSocket(ws0.getSelectFd(), 0));
+        auto handler1 = [&ws1](int) {
+            callback_ok = true;
+            EXPECT_NO_THROW(ws1.clearReady());
+        };
+        EXPECT_NO_THROW(ifacemgr->addExternalSocket(ws1.getSelectFd(), handler1));
+        auto handler2 = [&ws2](int) {
+            callback2_ok = true;
+            EXPECT_NO_THROW(ws2.clearReady());
+        };
+        EXPECT_NO_THROW(ifacemgr->addExternalSocket(ws2.getSelectFd(), handler2));
 
-#if 0
-TEST_F(IfaceMgrTest, dhcp6Sniffer) {
-    // Testing socket operation in a portable way is tricky
-    // without interface detection implemented
+        // Mark all watch sockets as ready.
+        ws0.markReady();
+        ws1.markReady();
+        ws2.markReady();
+        EXPECT_FALSE(callback_ok);
+        EXPECT_FALSE(callback2_ok);
 
-    static_cast<void>(remove("interfaces.txt"));
+        /// Check the order before the first call to receive6.
+        std::list<int> expected;
+        expected.push_back(ws0.getSelectFd());
+        expected.push_back(ws1.getSelectFd());
+        expected.push_back(ws2.getSelectFd());
+        EXPECT_EQ(expected, ifacemgr->getAllExternalSockets());
 
-    ofstream interfaces("interfaces.txt", ios::ate);
-    interfaces << "eth0 fe80::21e:8cff:fe9b:7349";
-    interfaces.close();
+        // First call to receive6: ws0 and ws1 are scanned and moved.
+        Pkt6Ptr pkt6;
+        ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10)));
+        EXPECT_TRUE(callback_ok);
+        EXPECT_FALSE(callback2_ok);
+        EXPECT_FALSE(pkt6);
+        expected.clear();
+        expected.push_back(ws2.getSelectFd());
+        expected.push_back(ws0.getSelectFd());
+        expected.push_back(ws1.getSelectFd());
+        EXPECT_EQ(expected, ifacemgr->getAllExternalSockets());
 
-    boost::scoped_ptr<NakedIfaceMgr> ifacemgr = new NakedIfaceMgr();
+        // Second call to receive6: ws2 is scanned and moved.
+        callback_ok = false;
+        ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10)));
+        EXPECT_FALSE(callback_ok);
+        EXPECT_TRUE(callback2_ok);
+        EXPECT_FALSE(pkt6);
+        expected.clear();
+        expected.push_back(ws0.getSelectFd());
+        expected.push_back(ws1.getSelectFd());
+        expected.push_back(ws2.getSelectFd());
+        EXPECT_EQ(expected, ifacemgr->getAllExternalSockets());
 
-    Pkt6Ptr pkt;
-    int cnt = 0;
-    cout << "---8X-----------------------------------------" << endl;
-    while (true) {
-        pkt.reset(ifacemgr->receive());
+        // Third call to receive6: ws0 is scanned and moved.
+        callback_ok = false;
+        callback2_ok = false;
+        ASSERT_NO_THROW(pkt6 = ifacemgr->receive6(RECEIVE_WAIT_MS(10)));
+        EXPECT_FALSE(callback_ok);
+        EXPECT_FALSE(callback2_ok);
+        EXPECT_FALSE(pkt6);
+        expected.clear();
+        expected.push_back(ws1.getSelectFd());
+        expected.push_back(ws2.getSelectFd());
+        expected.push_back(ws0.getSelectFd());
+        EXPECT_EQ(expected, ifacemgr->getAllExternalSockets());
 
-        cout << "// this code is autogenerated. Do NOT edit." << endl;
-        cout << "// Received " << pkt->data_len_ << " bytes packet:" << endl;
-        cout << "Pkt6 *capture" << cnt++ << "() {" << endl;
-        cout << "    Pkt6* pkt;" << endl;
-        cout << "    pkt = new Pkt6(" << pkt->data_len_ << ");" << endl;
-        cout << "    pkt->remote_port_ = " << pkt-> remote_port_ << ";" << endl;
-        cout << "    pkt->remote_addr_ = IOAddress(\""
-             << pkt->remote_addr_ << "\");" << endl;
-        cout << "    pkt->local_port_ = " << pkt-> local_port_ << ";" << endl;
-        cout << "    pkt->local_addr_ = IOAddress(\""
-             << pkt->local_addr_ << "\");" << endl;
-        cout << "    pkt->ifindex_ = " << pkt->ifindex_ << ";" << endl;
-        cout << "    pkt->iface_ = \"" << pkt->iface_ << "\";" << endl;
+        EXPECT_NO_THROW(ws0.clearReady());
+        std::string err;
+        EXPECT_NO_THROW(ws0.closeSocket(err));
+        EXPECT_EQ("", err);
+        EXPECT_NO_THROW(ws1.closeSocket(err));
+        EXPECT_EQ("", err);
+        EXPECT_NO_THROW(ws2.closeSocket(err));
+        EXPECT_EQ("", err);
+    }
 
-        // TODO it is better to declare statically initialize the array
-        // and then memcpy it to packet.
-        for (int i=0; i< pkt->data_len_; i++) {
-            cout << "    pkt->data_[" << i << "]="
-                 << (int)(unsigned char)pkt->data_[i] << "; ";
-            if (!(i%4))
-                cout << endl;
-        }
-        cout << endl;
-        cout << "    return (pkt);" << endl;
-        cout << "}" << endl << endl;
-
-        pkt.reset();
-    }
-    cout << "---8X-----------------------------------------" << endl;
+    /// @brief Test that all sockets are rotated when they are under load.
+    ///
+    /// @param direct Flag which indicates if direct or indirect receive should
+    /// be used.
+    void testReceive4RotateAll(bool direct = true) {
+        const size_t loop_count = 1024;
+        IfaceMgr::instance().clearIfaces();
+        IfaceMgr::instance().deleteAllExternalSockets();
+        callback_ok = false;
+        callback2_ok = false;
+        size_t callback_count = 0;
+        size_t callback2_count = 0;
+        PktFilterPtr filter(new PktFilter4IfaceSocketTest(true, false));
+        IfaceMgr::instance().setPacketFilter(filter);
+        IfacePtr iface0(new Iface("eth0", 0));
+        iface0->flag_up_ = true;
+        iface0->flag_running_ = true;
+        iface0->addAddress(IOAddress("192.168.0.1"));
+        IfaceMgr::instance().addInterface(iface0);
+        IfacePtr iface1(new Iface("eth1", 1));
+        iface1->flag_up_ = true;
+        iface1->flag_running_ = true;
+        iface1->addAddress(IOAddress("192.168.0.2"));
+        IfaceMgr::instance().addInterface(iface1);
+        IfacePtr iface2(new Iface("eth2", 2));
+        iface2->flag_up_ = true;
+        iface2->flag_running_ = true;
+        iface2->addAddress(IOAddress("192.168.0.3"));
+        IfaceMgr::instance().addInterface(iface2);
+        for (size_t i = 3; i < 250; ++i) {
+            string name = "eth";
+            name += std::to_string(i);
+            IfacePtr iface_n(new Iface(name, i));
+            iface_n->flag_up_ = true;
+            iface_n->flag_running_ = true;
+            iface_n->addAddress(IOAddress(string("192.168.0.") + std::to_string(i)));
+            IfaceMgr::instance().addInterface(iface_n);
+        }
+        if (!direct) {
+            auto queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, true);
+            IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, queue_control);
+        } else {
+            IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, data::ConstElementPtr());
+        }
+        IfaceMgr::instance().openSockets4(9999, true, IfaceMgrErrorMsgCallback(), false);
+        EXPECT_EQ((boost::dynamic_pointer_cast<PktFilter4IfaceSocketTest>(filter))->socket_fds_.size(), 250);
 
-    // Never happens. Infinite loop is infinite
-}
-#endif
+        // Create first pipe and register it as extra socket
+        int pipefd[2];
+        EXPECT_TRUE(pipe(pipefd) == 0);
+        EXPECT_NO_THROW(IfaceMgr::instance().addExternalSocket(pipefd[0], my_callback));
 
-// This test verifies that creation of the IfaceMgr instance doesn't
-// cause an exception.
-TEST_F(IfaceMgrTest, instance) {
-    EXPECT_NO_THROW(IfaceMgr::instance());
-}
+        // Let's create a second pipe and register it as well
+        int secondpipe[2];
+        EXPECT_TRUE(pipe(secondpipe) == 0);
+        EXPECT_NO_THROW(IfaceMgr::instance().addExternalSocket(secondpipe[0], my_callback2));
 
-// Basic tests for Iface inner class.
-TEST_F(IfaceMgrTest, ifaceClass) {
+        Pkt4Ptr pkt4;
+        ASSERT_NO_THROW(pkt4 = IfaceMgr::instance().receive4(1));
 
-  IfacePtr iface(new Iface("eth5", 7));
-    EXPECT_STREQ("eth5/7", iface->getFullName().c_str());
+        // Our callbacks should not be called this time (there was no data)
+        EXPECT_FALSE(callback_ok);
+        EXPECT_FALSE(callback2_ok);
 
-    EXPECT_THROW_MSG(iface.reset(new Iface("", 10)), BadValue,
-                     "Interface name must not be empty");
+        // IfaceMgr should not process control socket data as incoming packets
+        EXPECT_FALSE(pkt4);
 
-    EXPECT_NO_THROW(iface.reset(new Iface("big-index", 66666)));
-    EXPECT_EQ(66666, iface->getIndex());
-}
+        // Now, send some data over the first pipe (38 bytes)
+        EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38));
 
-// This test checks the getIface by index method.
-TEST_F(IfaceMgrTest, getIfaceByIndex) {
-    NakedIfaceMgr ifacemgr;
+        // And try again, using the second pipe
+        EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38));
 
-    // Create a set of fake interfaces. At the same time, remove the actual
-    // interfaces that have been detected by the IfaceMgr.
-    ifacemgr.createIfaces();
+        Pkt4Ptr pkt0(new Pkt4(DHCPDISCOVER, 1234));
+        pkt0->setIface("eth0");
+        pkt0->setIndex(0);
+        Pkt4Ptr pkt1(new Pkt4(DHCPDISCOVER, 2345));
+        pkt1->setIface("eth1");
+        pkt1->setIndex(1);
+        Pkt4Ptr pkt2(new Pkt4(DHCPDISCOVER, 3456));
+        pkt2->setIface("eth2");
+        pkt2->setIndex(2);
+        IfaceMgr::instance().send(pkt0);
+        IfaceMgr::instance().send(pkt1);
+        IfaceMgr::instance().send(pkt2);
+        std::unordered_map<Pkt4Ptr, size_t, hash_pkt4> expected;
+        expected[pkt0] = 0;
+        expected[pkt1] = 0;
+        expected[pkt2] = 0;
+        bool first = true;
+        size_t j = 0;
+        for (size_t i = 0; i < 3 * loop_count && j < 20 * loop_count;) {
+            Pkt4Ptr new_pkt = IfaceMgr::instance().receive4(1, 0);
+            if (first) {
+                first = false;
+                EXPECT_TRUE(callback_ok);
+                EXPECT_FALSE(callback2_ok);
+                callback_count++;
+            } else {
+                first = true;
+                EXPECT_FALSE(callback_ok);
+                EXPECT_TRUE(callback2_ok);
+                callback2_count++;
+            }
+            callback_ok = false;
+            callback2_ok = false;
+            if (new_pkt) {
+                expected[new_pkt]++;
+                if (i % 3 == 0) {
+                    EXPECT_EQ(new_pkt.get(), pkt0.get());
+                } else if (i % 3 == 1) {
+                    EXPECT_EQ(new_pkt.get(), pkt1.get());
+                } else {
+                    EXPECT_EQ(new_pkt.get(), pkt2.get());
+                }
+                i++;
+            } else {
+                j++;
+            }
+        }
 
-    // Getting an unset index should throw.
-    EXPECT_THROW_MSG(ifacemgr.getIface(UNSET_IFINDEX), BadValue, "interface index was not set");
+        for (auto i = expected.begin(); i!= expected.end(); ++i) {
+            EXPECT_EQ(loop_count, i->second);
+        }
 
-    // Historically -1 was used as an unset value. Let's also check that it throws in case we didn't
-    // migrate all code to UNSET_IFINDEX and in case the values diverge.
-    EXPECT_THROW_MSG(ifacemgr.getIface(-1), BadValue, "interface index was not set");
+        EXPECT_EQ((3 * loop_count + j) / 2 + j % 2, callback_count);
+        EXPECT_EQ((3 * loop_count + j) / 2, callback2_count);
 
-    // Get the first interface defined.
-    IfacePtr iface(ifacemgr.getIface(0));
-    ASSERT_TRUE(iface);
-    EXPECT_EQ("lo", iface->getName());
+        // close both pipe ends
+        close(pipefd[1]);
+        close(pipefd[0]);
 
-    // Attemt to get an undefined interface.
-    iface = ifacemgr.getIface(3);
-    EXPECT_FALSE(iface);
+        close(secondpipe[1]);
+        close(secondpipe[0]);
 
-    // Check that we can go past INT_MAX.
-    unsigned int int_max(numeric_limits<int>::max());
-    iface = ifacemgr.getIface(int_max);
-    EXPECT_FALSE(iface);
-    iface = ifacemgr.createIface("wlan0", int_max);
-    ifacemgr.addInterface(iface);
-    iface = ifacemgr.getIface(int_max);
-    EXPECT_TRUE(iface);
-    iface = ifacemgr.getIface(int_max + 1);
-    EXPECT_FALSE(iface);
-    iface = ifacemgr.createIface("wlan1", int_max + 1);
-    ifacemgr.addInterface(iface);
-    iface = ifacemgr.getIface(int_max + 1);
-    EXPECT_TRUE(iface);
-}
+        ASSERT_NO_THROW(IfaceMgr::instance().stopDHCPReceiver());
+    }
 
-// This test checks the getIface by packet method.
-TEST_F(IfaceMgrTest, getIfaceByPkt) {
-    NakedIfaceMgr ifacemgr;
-    // Create a set of fake interfaces. At the same time, remove the actual
-    // interfaces that have been detected by the IfaceMgr.
-    ifacemgr.createIfaces();
+    /// @brief Test that all sockets are rotated when they are under load.
+    ///
+    /// @param direct Flag which indicates if direct or indirect receive should
+    /// be used.
+    void testReceive6RotateAll(bool direct = true) {
+        const size_t loop_count = 1024;
+        IfaceMgr::instance().clearIfaces();
+        IfaceMgr::instance().deleteAllExternalSockets();
+        callback_ok = false;
+        callback2_ok = false;
+        size_t callback_count = 0;
+        size_t callback2_count = 0;
+        PktFilter6Ptr filter(new PktFilter6IfaceSocketTest(true, false));
+        IfaceMgr::instance().setPacketFilter(filter);
+        IfacePtr iface0(new Iface("eth0", 0));
+        iface0->flag_up_ = true;
+        iface0->flag_running_ = true;
+        iface0->addAddress(IOAddress("2003:db8::1"));
+        iface0->addAddress(IOAddress("fe80::3a60:77ff:fed5:abcd"));
+        IfaceMgr::instance().addInterface(iface0);
+        IfacePtr iface1(new Iface("eth1", 1));
+        iface1->flag_up_ = true;
+        iface1->flag_running_ = true;
+        iface1->addAddress(IOAddress("2003:db8::2"));
+        iface1->addAddress(IOAddress("fe80::3a60:77ff:fed5:bcde"));
+        IfaceMgr::instance().addInterface(iface1);
+        IfacePtr iface2(new Iface("eth2", 2));
+        iface2->flag_up_ = true;
+        iface2->flag_running_ = true;
+        iface2->addAddress(IOAddress("2003:db8::3"));
+        iface2->addAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"));
+        IfaceMgr::instance().addInterface(iface2);
+        for (size_t i = 3; i < 250; ++i) {
+            string name = "eth";
+            name += std::to_string(i);
+            IfacePtr iface_n(new Iface(name, i));
+            iface_n->flag_up_ = true;
+            iface_n->flag_running_ = true;
+            iface_n->addAddress(IOAddress(string("2003:db8::") + std::to_string(i)));
+            iface_n->addAddress(IOAddress(string("fe80::3a60:77ff:fed5:") + std::to_string(i)));
+            IfaceMgr::instance().addInterface(iface_n);
+        }
+        if (!direct) {
+            auto queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, true);
+            IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, queue_control);
+        } else {
+            IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, data::ConstElementPtr());
+        }
+        IfaceMgr::instance().openSockets6(9999, IfaceMgrErrorMsgCallback(), false);
+        EXPECT_EQ((boost::dynamic_pointer_cast<PktFilter6IfaceSocketTest>(filter))->socket_fds_.size(), 250);
 
-    // Try IPv4 packet by name.
-    Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 1234));
-    IfacePtr iface = ifacemgr.getIface(pkt4);
-    EXPECT_FALSE(iface);
-    pkt4->setIface("eth0");
-    iface = ifacemgr.getIface(pkt4);
-    EXPECT_TRUE(iface);
-    EXPECT_FALSE(pkt4->indexSet());
+        // Create first pipe and register it as extra socket
+        int pipefd[2];
+        EXPECT_TRUE(pipe(pipefd) == 0);
+        EXPECT_NO_THROW(IfaceMgr::instance().addExternalSocket(pipefd[0], my_callback));
 
-    // Try IPv6 packet by index.
-    Pkt6Ptr pkt6(new Pkt6(DHCPV6_REPLY, 123456));
-    iface = ifacemgr.getIface(pkt6);
-    EXPECT_FALSE(iface);
-    ASSERT_TRUE(ifacemgr.getIface("eth0"));
-    pkt6->setIndex(ifacemgr.getIface("eth0")->getIndex() + 1);
-    iface = ifacemgr.getIface(pkt6);
-    ASSERT_TRUE(iface);
-    EXPECT_TRUE(pkt6->indexSet());
+        // Let's create a second pipe and register it as well
+        int secondpipe[2];
+        EXPECT_TRUE(pipe(secondpipe) == 0);
+        EXPECT_NO_THROW(IfaceMgr::instance().addExternalSocket(secondpipe[0], my_callback2));
 
-    // Index has precedence when both name and index are available.
-    EXPECT_EQ("eth1", iface->getName());
-    pkt6->setIface("eth0");
-    iface = ifacemgr.getIface(pkt6);
-    ASSERT_TRUE(iface);
-    EXPECT_EQ("eth1", iface->getName());
+        Pkt6Ptr pkt6;
+        ASSERT_NO_THROW(pkt6 = IfaceMgr::instance().receive6(1));
 
-    // Not existing name fails.
-    pkt4->setIface("eth2");
-    iface = ifacemgr.getIface(pkt4);
-    EXPECT_FALSE(iface);
+        // Our callbacks should not be called this time (there was no data)
+        EXPECT_FALSE(callback_ok);
+        EXPECT_FALSE(callback2_ok);
 
-    // Not existing index fails.
-    pkt6->setIndex(3);
-    iface = ifacemgr.getIface(pkt6);
-    ASSERT_FALSE(iface);
+        // IfaceMgr should not process control socket data as incoming packets
+        EXPECT_FALSE(pkt6);
 
-    // Test that resetting the index is verifiable.
-    pkt4->resetIndex();
-    EXPECT_FALSE(pkt4->indexSet());
-    pkt6->resetIndex();
-    EXPECT_FALSE(pkt6->indexSet());
+        // Now, send some data over the first pipe (38 bytes)
+        EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38));
 
-    // Test that you can also reset the index via setIndex().
-    pkt4->setIndex(UNSET_IFINDEX);
-    EXPECT_FALSE(pkt4->indexSet());
-    pkt6->setIndex(UNSET_IFINDEX);
-    EXPECT_FALSE(pkt6->indexSet());
-}
+        // And try again, using the second pipe
+        EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38));
 
-// Test that the IPv4 address can be retrieved for the interface.
-TEST_F(IfaceMgrTest, ifaceGetAddress) {
-    Iface iface("eth0", 0);
+        Pkt6Ptr pkt0(new Pkt6(DHCPV6_SOLICIT, 1234));
+        pkt0->setIface("eth0");
+        pkt0->setIndex(0);
+        Pkt6Ptr pkt1(new Pkt6(DHCPV6_SOLICIT, 2345));
+        pkt1->setIface("eth1");
+        pkt1->setIndex(1);
+        Pkt6Ptr pkt2(new Pkt6(DHCPV6_SOLICIT, 3456));
+        pkt2->setIface("eth2");
+        pkt2->setIndex(2);
+        IfaceMgr::instance().send(pkt0);
+        IfaceMgr::instance().send(pkt1);
+        IfaceMgr::instance().send(pkt2);
+        std::unordered_map<Pkt6Ptr, size_t, hash_pkt6> expected;
+        expected[pkt0] = 0;
+        expected[pkt1] = 0;
+        expected[pkt2] = 0;
+        bool first = true;
+        size_t j = 0;
+        for (size_t i = 0; i < 3 * loop_count && j < 20 * loop_count;) {
+            Pkt6Ptr new_pkt = IfaceMgr::instance().receive6(1, 0);
+            if (first) {
+                first = false;
+                EXPECT_TRUE(callback_ok);
+                EXPECT_FALSE(callback2_ok);
+                callback_count++;
+            } else {
+                first = true;
+                EXPECT_FALSE(callback_ok);
+                EXPECT_TRUE(callback2_ok);
+                callback2_count++;
+            }
+            callback_ok = false;
+            callback2_ok = false;
+            if (new_pkt) {
+                expected[new_pkt]++;
+                if (i % 3 == 0) {
+                    EXPECT_EQ(new_pkt.get(), pkt0.get());
+                } else if (i % 3 == 1) {
+                    EXPECT_EQ(new_pkt.get(), pkt1.get());
+                } else {
+                    EXPECT_EQ(new_pkt.get(), pkt2.get());
+                }
+                i++;
+            } else {
+                j++;
+            }
+        }
+
+        for (auto i = expected.begin(); i!= expected.end(); ++i) {
+            EXPECT_EQ(loop_count, i->second);
+        }
+
+        EXPECT_EQ((3 * loop_count + j) / 2 + j % 2, callback_count);
+        EXPECT_EQ((3 * loop_count + j) / 2, callback2_count);
+
+        // close both pipe ends
+        close(pipefd[1]);
+        close(pipefd[0]);
+
+        close(secondpipe[1]);
+        close(secondpipe[0]);
+
+        ASSERT_NO_THROW(IfaceMgr::instance().stopDHCPReceiver());
+    }
+
+    /// @brief Test that iface sockets are rotated when ifaces are under load.
+    ///
+    /// @param direct Flag which indicates if direct or indirect receive should
+    /// be used.
+    void testReceive4RotateIfaces(bool direct = true) {
+        const size_t loop_count = 1024;
+        IfaceMgr::instance().clearIfaces();
+        IfaceMgr::instance().deleteAllExternalSockets();
+        PktFilterPtr filter(new PktFilter4IfaceSocketTest(true, false));
+        IfaceMgr::instance().setPacketFilter(filter);
+        IfacePtr iface0(new Iface("eth0", 0));
+        iface0->flag_up_ = true;
+        iface0->flag_running_ = true;
+        iface0->addAddress(IOAddress("192.168.0.1"));
+        IfaceMgr::instance().addInterface(iface0);
+        IfacePtr iface1(new Iface("eth1", 1));
+        iface1->flag_up_ = true;
+        iface1->flag_running_ = true;
+        iface1->addAddress(IOAddress("192.168.0.2"));
+        IfaceMgr::instance().addInterface(iface1);
+        IfacePtr iface2(new Iface("eth2", 2));
+        iface2->flag_up_ = true;
+        iface2->flag_running_ = true;
+        iface2->addAddress(IOAddress("192.168.0.3"));
+        IfaceMgr::instance().addInterface(iface2);
+        for (size_t i = 3; i < 250; ++i) {
+            string name = "eth";
+            name += std::to_string(i);
+            IfacePtr iface_n(new Iface(name, i));
+            iface_n->flag_up_ = true;
+            iface_n->flag_running_ = true;
+            iface_n->addAddress(IOAddress(string("192.168.0.") + std::to_string(i)));
+            IfaceMgr::instance().addInterface(iface_n);
+        }
+        if (!direct) {
+            auto queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, true);
+            IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, queue_control);
+        } else {
+            IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, data::ConstElementPtr());
+        }
+        IfaceMgr::instance().openSockets4(9999, true, IfaceMgrErrorMsgCallback(), false);
+        EXPECT_EQ((boost::dynamic_pointer_cast<PktFilter4IfaceSocketTest>(filter))->socket_fds_.size(), 250);
+        Pkt4Ptr pkt0(new Pkt4(DHCPDISCOVER, 1234));
+        pkt0->setIface("eth0");
+        pkt0->setIndex(0);
+        Pkt4Ptr pkt1(new Pkt4(DHCPDISCOVER, 2345));
+        pkt1->setIface("eth1");
+        pkt1->setIndex(1);
+        Pkt4Ptr pkt2(new Pkt4(DHCPDISCOVER, 3456));
+        pkt2->setIface("eth2");
+        pkt2->setIndex(2);
+        IfaceMgr::instance().send(pkt0);
+        IfaceMgr::instance().send(pkt1);
+        IfaceMgr::instance().send(pkt2);
+        std::unordered_map<Pkt4Ptr, size_t, hash_pkt4> expected;
+        expected[pkt0] = 0;
+        expected[pkt1] = 0;
+        expected[pkt2] = 0;
+        for (size_t i = 0, j = 0; i < 3 * loop_count && j < 2 * loop_count;) {
+            Pkt4Ptr new_pkt = IfaceMgr::instance().receive4(1, 0);
+            if (new_pkt) {
+                expected[new_pkt]++;
+                if (i % 3 == 0) {
+                    EXPECT_EQ(new_pkt.get(), pkt0.get());
+                } else if (i % 3 == 1) {
+                    EXPECT_EQ(new_pkt.get(), pkt1.get());
+                } else {
+                    EXPECT_EQ(new_pkt.get(), pkt2.get());
+                }
+                i++;
+            } else {
+                j++;
+            }
+        }
+        for (auto i = expected.begin(); i!= expected.end(); ++i) {
+            EXPECT_EQ(loop_count, i->second);
+        }
+        ASSERT_NO_THROW(IfaceMgr::instance().stopDHCPReceiver());
+    }
+
+    /// @brief Test that iface sockets are rotated when ifaces are under load.
+    ///
+    /// @param direct Flag which indicates if direct or indirect receive should
+    /// be used.
+    void testReceive6RotateIfaces(bool direct = true) {
+        const size_t loop_count = 1024;
+        IfaceMgr::instance().clearIfaces();
+        IfaceMgr::instance().deleteAllExternalSockets();
+        PktFilter6Ptr filter(new PktFilter6IfaceSocketTest(true, false));
+        IfaceMgr::instance().setPacketFilter(filter);
+        IfacePtr iface0(new Iface("eth0", 0));
+        iface0->flag_up_ = true;
+        iface0->flag_running_ = true;
+        iface0->addAddress(IOAddress("2003:db8::1"));
+        iface0->addAddress(IOAddress("fe80::3a60:77ff:fed5:abcd"));
+        IfaceMgr::instance().addInterface(iface0);
+        IfacePtr iface1(new Iface("eth1", 1));
+        iface1->flag_up_ = true;
+        iface1->flag_running_ = true;
+        iface1->addAddress(IOAddress("2003:db8::2"));
+        iface1->addAddress(IOAddress("fe80::3a60:77ff:fed5:bcde"));
+        IfaceMgr::instance().addInterface(iface1);
+        IfacePtr iface2(new Iface("eth2", 2));
+        iface2->flag_up_ = true;
+        iface2->flag_running_ = true;
+        iface2->addAddress(IOAddress("2003:db8::3"));
+        iface2->addAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"));
+        IfaceMgr::instance().addInterface(iface2);
+        for (size_t i = 3; i < 250; ++i) {
+            string name = "eth";
+            name += std::to_string(i);
+            IfacePtr iface_n(new Iface(name, i));
+            iface_n->flag_up_ = true;
+            iface_n->flag_running_ = true;
+            iface_n->addAddress(IOAddress(string("2003:db8::") + std::to_string(i)));
+            iface_n->addAddress(IOAddress(string("fe80::3a60:77ff:fed5:") + std::to_string(i)));
+            IfaceMgr::instance().addInterface(iface_n);
+        }
+        if (!direct) {
+            auto queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, true);
+            IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, queue_control);
+        } else {
+            IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, data::ConstElementPtr());
+        }
+        IfaceMgr::instance().openSockets6(9999, IfaceMgrErrorMsgCallback(), false);
+        EXPECT_EQ((boost::dynamic_pointer_cast<PktFilter6IfaceSocketTest>(filter))->socket_fds_.size(), 250);
+        Pkt6Ptr pkt0(new Pkt6(DHCPV6_SOLICIT, 1234));
+        pkt0->setIface("eth0");
+        pkt0->setIndex(0);
+        Pkt6Ptr pkt1(new Pkt6(DHCPV6_SOLICIT, 2345));
+        pkt1->setIface("eth1");
+        pkt1->setIndex(1);
+        Pkt6Ptr pkt2(new Pkt6(DHCPV6_SOLICIT, 3456));
+        pkt2->setIface("eth2");
+        pkt2->setIndex(2);
+        IfaceMgr::instance().send(pkt0);
+        IfaceMgr::instance().send(pkt1);
+        IfaceMgr::instance().send(pkt2);
+        std::unordered_map<Pkt6Ptr, size_t, hash_pkt6> expected;
+        expected[pkt0] = 0;
+        expected[pkt1] = 0;
+        expected[pkt2] = 0;
+        for (size_t i = 0, j = 0; i < 3 * loop_count && j < 2 * loop_count;) {
+            Pkt6Ptr new_pkt = IfaceMgr::instance().receive6(1, 0);
+            if (new_pkt) {
+                expected[new_pkt]++;
+                if (i % 3 == 0) {
+                    EXPECT_EQ(new_pkt.get(), pkt0.get());
+                } else if (i % 3 == 1) {
+                    EXPECT_EQ(new_pkt.get(), pkt1.get());
+                } else {
+                    EXPECT_EQ(new_pkt.get(), pkt2.get());
+                }
+                i++;
+            } else {
+                j++;
+            }
+        }
+        for (auto i = expected.begin(); i!= expected.end(); ++i) {
+            EXPECT_EQ(loop_count, i->second);
+        }
+        ASSERT_NO_THROW(IfaceMgr::instance().stopDHCPReceiver());
+    }
+
+    /// @brief Test that external sockets are rotated when external sockets are under load.
+    ///
+    /// @param direct Flag which indicates if direct or indirect receive should
+    /// be used.
+    void testExternalSockets4Rotation(bool direct = true) {
+        IfaceMgr::instance().deleteAllExternalSockets();
+        callback_ok = false;
+        callback2_ok = false;
+        size_t callback_count = 0;
+        size_t callback2_count = 0;
+
+        size_t loop_count = 1024;
+
+        if (!direct) {
+            auto queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, true);
+            IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, queue_control);
+        } else {
+            IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, data::ConstElementPtr());
+        }
+        IfaceMgr::instance().startDHCPReceiver(AF_INET);
+
+        // Create first pipe and register it as extra socket
+        int pipefd[2];
+        EXPECT_TRUE(pipe(pipefd) == 0);
+        EXPECT_NO_THROW(IfaceMgr::instance().addExternalSocket(pipefd[0], my_callback));
+
+        // Let's create a second pipe and register it as well
+        int secondpipe[2];
+        EXPECT_TRUE(pipe(secondpipe) == 0);
+        EXPECT_NO_THROW(IfaceMgr::instance().addExternalSocket(secondpipe[0], my_callback2));
+
+        Pkt4Ptr pkt4;
+        ASSERT_NO_THROW(pkt4 = IfaceMgr::instance().receive4(1));
+
+        // Our callbacks should not be called this time (there was no data)
+        EXPECT_FALSE(callback_ok);
+        EXPECT_FALSE(callback2_ok);
+
+        // IfaceMgr should not process control socket data as incoming packets
+        EXPECT_FALSE(pkt4);
+
+        // Now, send some data over the first pipe (38 bytes)
+        EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38));
+
+        // And try again, using the second pipe
+        EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38));
+
+        for (size_t i = 0; i < loop_count; ++i) {
+            // Clear the status...
+            callback_ok = false;
+            callback2_ok = false;
+
+            // ... and repeat
+            ASSERT_NO_THROW(pkt4 = IfaceMgr::instance().receive4(1));
+
+            // IfaceMgr should not process control socket data as incoming packets
+            EXPECT_FALSE(pkt4);
+
+            // There was some data, so this time callback should be called
+            EXPECT_TRUE(callback_ok);
+            EXPECT_FALSE(callback2_ok);
+            callback_count++;
+            EXPECT_EQ(i + 1, callback_count);
+
+            // Clear the status...
+            callback_ok = false;
+            callback2_ok = false;
+
+            // ... and repeat
+            ASSERT_NO_THROW(pkt4 = IfaceMgr::instance().receive4(1));
+
+            // IfaceMgr should not process control socket data as incoming packets
+            EXPECT_FALSE(pkt4);
+
+            // There was some data, so this time second callback should be called
+            // Without socket rotation this would call the first callback as the first
+            // pipe data has not been read.
+            EXPECT_FALSE(callback_ok);
+            EXPECT_TRUE(callback2_ok);
+            callback2_count++;
+            EXPECT_EQ(i + 1, callback2_count);
+        }
+
+        EXPECT_EQ(loop_count, callback_count);
+        EXPECT_EQ(loop_count, callback2_count);
+
+        // close both pipe ends
+        close(pipefd[1]);
+        close(pipefd[0]);
+
+        close(secondpipe[1]);
+        close(secondpipe[0]);
+
+        ASSERT_NO_THROW(IfaceMgr::instance().stopDHCPReceiver());
+    }
+
+    /// @brief Test that external sockets are rotated when external sockets are under load.
+    ///
+    /// @param direct Flag which indicates if direct or indirect receive should
+    /// be used.
+    void testExternalSockets6Rotation(bool direct = true) {
+        IfaceMgr::instance().deleteAllExternalSockets();
+        callback_ok = false;
+        callback2_ok = false;
+        size_t callback_count = 0;
+        size_t callback2_count = 0;
+
+        size_t loop_count = 1024;
+
+        if (!direct) {
+            auto queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, true);
+            IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, queue_control);
+        } else {
+            IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, data::ConstElementPtr());
+        }
+        IfaceMgr::instance().startDHCPReceiver(AF_INET6);
+
+        // Create first pipe and register it as extra socket
+        int pipefd[2];
+        EXPECT_TRUE(pipe(pipefd) == 0);
+        EXPECT_NO_THROW(IfaceMgr::instance().addExternalSocket(pipefd[0], my_callback));
+
+        // Let's create a second pipe and register it as well
+        int secondpipe[2];
+        EXPECT_TRUE(pipe(secondpipe) == 0);
+        EXPECT_NO_THROW(IfaceMgr::instance().addExternalSocket(secondpipe[0], my_callback2));
+
+        Pkt6Ptr pkt6;
+        ASSERT_NO_THROW(pkt6 = IfaceMgr::instance().receive6(1));
+
+        // Our callbacks should not be called this time (there was no data)
+        EXPECT_FALSE(callback_ok);
+        EXPECT_FALSE(callback2_ok);
+
+        // IfaceMgr should not process control socket data as incoming packets
+        EXPECT_FALSE(pkt6);
+
+        // Now, send some data over the first pipe (38 bytes)
+        EXPECT_EQ(38, write(pipefd[1], "Hi, this is a message sent over a pipe", 38));
+
+        // And try again, using the second pipe
+        EXPECT_EQ(38, write(secondpipe[1], "Hi, this is a message sent over a pipe", 38));
+
+        for (size_t i = 0; i < loop_count; ++i) {
+            // Clear the status...
+            callback_ok = false;
+            callback2_ok = false;
+
+            // ... and repeat
+            ASSERT_NO_THROW(pkt6 = IfaceMgr::instance().receive6(1));
+
+            // IfaceMgr should not process control socket data as incoming packets
+            EXPECT_FALSE(pkt6);
+
+            // There was some data, so this time callback should be called
+            EXPECT_TRUE(callback_ok);
+            EXPECT_FALSE(callback2_ok);
+            callback_count++;
+            EXPECT_EQ(i + 1, callback_count);
+
+            // Clear the status...
+            callback_ok = false;
+            callback2_ok = false;
+
+            // ... and repeat
+            ASSERT_NO_THROW(pkt6 = IfaceMgr::instance().receive6(1));
+
+            // IfaceMgr should not process control socket data as incoming packets
+            EXPECT_FALSE(pkt6);
+
+            // There was some data, so this time second callback should be called
+            // Without socket rotation this would call the first callback as the first
+            // pipe data has not been read.
+            EXPECT_FALSE(callback_ok);
+            EXPECT_TRUE(callback2_ok);
+            callback2_count++;
+            EXPECT_EQ(i + 1, callback2_count);
+        }
+
+        EXPECT_EQ(loop_count, callback_count);
+        EXPECT_EQ(loop_count, callback2_count);
+
+        // close both pipe ends
+        close(pipefd[1]);
+        close(pipefd[0]);
+
+        close(secondpipe[1]);
+        close(secondpipe[0]);
+
+        ASSERT_NO_THROW(IfaceMgr::instance().stopDHCPReceiver());
+    }
+
+    /// @brief Holds the invocation counter for ifaceMgrErrorHandler.
+    int errors_count_;
+
+    /// @brief RAII wrapper for KEA_EVENT_HANDLER_TYPE env variable.
+    EnvVarWrapper kea_event_handler_type_;
+};
+
+// We need some known interface to work reliably. Loopback interface is named
+// lo on Linux and lo0 on BSD boxes. We need to find out which is available.
+// This is not a real test, but rather a workaround that will go away when
+// interface detection is implemented on all OSes.
+TEST_F(IfaceMgrTest, loDetect) {
+    NakedIfaceMgr::loDetect();
+}
+
+// Uncomment this test to create packet writer. It will
+// write incoming DHCPv6 packets as C arrays. That is useful
+// for generating test sequences based on actual traffic
+//
+// TODO: this potentially should be moved to a separate tool
+//
+
+#if 0
+TEST_F(IfaceMgrTest, dhcp6Sniffer) {
+    // Testing socket operation in a portable way is tricky
+    // without interface detection implemented
+
+    static_cast<void>(remove("interfaces.txt"));
+
+    ofstream interfaces("interfaces.txt", ios::ate);
+    interfaces << "eth0 fe80::21e:8cff:fe9b:7349";
+    interfaces.close();
+
+    boost::scoped_ptr<NakedIfaceMgr> ifacemgr = new NakedIfaceMgr();
+
+    Pkt6Ptr pkt;
+    int cnt = 0;
+    cout << "---8X-----------------------------------------" << endl;
+    while (true) {
+        pkt.reset(ifacemgr->receive());
+
+        cout << "// this code is autogenerated. Do NOT edit." << endl;
+        cout << "// Received " << pkt->data_len_ << " bytes packet:" << endl;
+        cout << "Pkt6 *capture" << cnt++ << "() {" << endl;
+        cout << "    Pkt6* pkt;" << endl;
+        cout << "    pkt = new Pkt6(" << pkt->data_len_ << ");" << endl;
+        cout << "    pkt->remote_port_ = " << pkt-> remote_port_ << ";" << endl;
+        cout << "    pkt->remote_addr_ = IOAddress(\""
+             << pkt->remote_addr_ << "\");" << endl;
+        cout << "    pkt->local_port_ = " << pkt-> local_port_ << ";" << endl;
+        cout << "    pkt->local_addr_ = IOAddress(\""
+             << pkt->local_addr_ << "\");" << endl;
+        cout << "    pkt->ifindex_ = " << pkt->ifindex_ << ";" << endl;
+        cout << "    pkt->iface_ = \"" << pkt->iface_ << "\";" << endl;
+
+        // TODO it is better to declare statically initialize the array
+        // and then memcpy it to packet.
+        for (int i=0; i< pkt->data_len_; i++) {
+            cout << "    pkt->data_[" << i << "]="
+                 << (int)(unsigned char)pkt->data_[i] << "; ";
+            if (!(i%4))
+                cout << endl;
+        }
+        cout << endl;
+        cout << "    return (pkt);" << endl;
+        cout << "}" << endl << endl;
+
+        pkt.reset();
+    }
+    cout << "---8X-----------------------------------------" << endl;
+
+    // Never happens. Infinite loop is infinite
+}
+#endif
+
+// This test verifies that creation of the IfaceMgr instance doesn't
+// cause an exception.
+TEST_F(IfaceMgrTest, instance) {
+    EXPECT_NO_THROW(IfaceMgr::instance());
+}
+
+// Basic tests for Iface inner class.
+TEST_F(IfaceMgrTest, ifaceClass) {
+
+  IfacePtr iface(new Iface("eth5", 7));
+    EXPECT_STREQ("eth5/7", iface->getFullName().c_str());
+
+    EXPECT_THROW_MSG(iface.reset(new Iface("", 10)), BadValue,
+                     "Interface name must not be empty");
+
+    EXPECT_NO_THROW(iface.reset(new Iface("big-index", 66666)));
+    EXPECT_EQ(66666, iface->getIndex());
+}
+
+// This test checks the getIface by index method.
+TEST_F(IfaceMgrTest, getIfaceByIndex) {
+    NakedIfaceMgr ifacemgr;
+
+    // Create a set of fake interfaces. At the same time, remove the actual
+    // interfaces that have been detected by the IfaceMgr.
+    ifacemgr.createIfaces();
+
+    // Getting an unset index should throw.
+    EXPECT_THROW_MSG(ifacemgr.getIface(UNSET_IFINDEX), BadValue, "interface index was not set");
+
+    // Historically -1 was used as an unset value. Let's also check that it throws in case we didn't
+    // migrate all code to UNSET_IFINDEX and in case the values diverge.
+    EXPECT_THROW_MSG(ifacemgr.getIface(-1), BadValue, "interface index was not set");
+
+    // Get the first interface defined.
+    IfacePtr iface(ifacemgr.getIface(0));
+    ASSERT_TRUE(iface);
+    EXPECT_EQ("lo", iface->getName());
+
+    // Attemt to get an undefined interface.
+    iface = ifacemgr.getIface(3);
+    EXPECT_FALSE(iface);
+
+    // Check that we can go past INT_MAX.
+    unsigned int int_max(numeric_limits<int>::max());
+    iface = ifacemgr.getIface(int_max);
+    EXPECT_FALSE(iface);
+    iface = ifacemgr.createIface("wlan0", int_max);
+    ifacemgr.addInterface(iface);
+    iface = ifacemgr.getIface(int_max);
+    EXPECT_TRUE(iface);
+    iface = ifacemgr.getIface(int_max + 1);
+    EXPECT_FALSE(iface);
+    iface = ifacemgr.createIface("wlan1", int_max + 1);
+    ifacemgr.addInterface(iface);
+    iface = ifacemgr.getIface(int_max + 1);
+    EXPECT_TRUE(iface);
+}
+
+// This test checks the getIface by packet method.
+TEST_F(IfaceMgrTest, getIfaceByPkt) {
+    NakedIfaceMgr ifacemgr;
+    // Create a set of fake interfaces. At the same time, remove the actual
+    // interfaces that have been detected by the IfaceMgr.
+    ifacemgr.createIfaces();
+
+    // Try IPv4 packet by name.
+    Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 1234));
+    IfacePtr iface = ifacemgr.getIface(pkt4);
+    EXPECT_FALSE(iface);
+    pkt4->setIface("eth0");
+    iface = ifacemgr.getIface(pkt4);
+    EXPECT_TRUE(iface);
+    EXPECT_FALSE(pkt4->indexSet());
+
+    // Try IPv6 packet by index.
+    Pkt6Ptr pkt6(new Pkt6(DHCPV6_REPLY, 123456));
+    iface = ifacemgr.getIface(pkt6);
+    EXPECT_FALSE(iface);
+    ASSERT_TRUE(ifacemgr.getIface("eth0"));
+    pkt6->setIndex(ifacemgr.getIface("eth0")->getIndex() + 1);
+    iface = ifacemgr.getIface(pkt6);
+    ASSERT_TRUE(iface);
+    EXPECT_TRUE(pkt6->indexSet());
+
+    // Index has precedence when both name and index are available.
+    EXPECT_EQ("eth1", iface->getName());
+    pkt6->setIface("eth0");
+    iface = ifacemgr.getIface(pkt6);
+    ASSERT_TRUE(iface);
+    EXPECT_EQ("eth1", iface->getName());
+
+    // Not existing name fails.
+    pkt4->setIface("eth2");
+    iface = ifacemgr.getIface(pkt4);
+    EXPECT_FALSE(iface);
+
+    // Not existing index fails.
+    pkt6->setIndex(3);
+    iface = ifacemgr.getIface(pkt6);
+    ASSERT_FALSE(iface);
+
+    // Test that resetting the index is verifiable.
+    pkt4->resetIndex();
+    EXPECT_FALSE(pkt4->indexSet());
+    pkt6->resetIndex();
+    EXPECT_FALSE(pkt6->indexSet());
+
+    // Test that you can also reset the index via setIndex().
+    pkt4->setIndex(UNSET_IFINDEX);
+    EXPECT_FALSE(pkt4->indexSet());
+    pkt6->setIndex(UNSET_IFINDEX);
+    EXPECT_FALSE(pkt6->indexSet());
+}
+
+// Test that the IPv4 address can be retrieved for the interface.
+TEST_F(IfaceMgrTest, ifaceGetAddress) {
+    Iface iface("eth0", 0);
 
     IOAddress addr("::1");
     // Initially, the Iface has no addresses assigned.
@@ -1358,14 +2319,14 @@ TEST_F(IfaceMgrTest, getIface) {
          << " or wifi15 interfaces present." << endl;
 
     // Note: real interfaces may be detected as well
-    ifacemgr->getIfacesLst().push_back(iface1);
-    ifacemgr->getIfacesLst().push_back(iface2);
-    ifacemgr->getIfacesLst().push_back(iface3);
-    ifacemgr->getIfacesLst().push_back(iface4);
+    ifacemgr->addInterface(iface1);
+    ifacemgr->addInterface(iface2);
+    ifacemgr->addInterface(iface3);
+    ifacemgr->addInterface(iface4);
 
-    cout << "There are " << ifacemgr->getIfacesLst().size()
+    cout << "There are " << ifacemgr->getIfaces().size()
          << " interfaces." << endl;
-    for (auto const& iface : ifacemgr->getIfacesLst()) {
+    for (auto const& iface : ifacemgr->getIfaces()) {
         cout << "  " << iface->getFullName() << endl;
     }
 
@@ -2763,7 +3724,7 @@ TEST_F(IfaceMgrTest, openSockets6IfaceInactive) {
 TEST_F(IfaceMgrTest, openSockets6NoIfaces) {
     NakedIfaceMgr ifacemgr;
     // Remove existing interfaces.
-    ifacemgr.getIfacesLst().clear();
+    ifacemgr.clearIfaces();
 
     boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
     ASSERT_TRUE(filter);
@@ -3408,6 +4369,16 @@ void IfaceMgrTest::testMultipleExternalSockets4() {
     close(secondpipe[0]);
 }
 
+TEST_F(IfaceMgrTest, directMultipleExternalSockets4) {
+    testExternalSockets4Rotation();
+}
+
+// Tests if multiple external sockets and their callbacks can be passed and
+// it is supported properly by receive4() method.
+TEST_F(IfaceMgrTest, indirectMultipleExternalSockets4) {
+    testExternalSockets4Rotation(false);
+}
+
 TEST_F(IfaceMgrTest, MultipleExternalSockets4Select) {
     kea_event_handler_type_.setValue("select");
     testMultipleExternalSockets4();
@@ -3633,6 +4604,16 @@ void IfaceMgrTest::testMultipleExternalSockets6() {
     close(secondpipe[0]);
 }
 
+TEST_F(IfaceMgrTest, directMultipleExternalSockets6) {
+    testExternalSockets6Rotation();
+}
+
+// Tests if multiple external sockets and their callbacks can be passed and
+// it is supported properly by receive6() method.
+TEST_F(IfaceMgrTest, indirectMultipleExternalSockets6) {
+    testExternalSockets6Rotation(false);
+}
+
 TEST_F(IfaceMgrTest, MultipleExternalSockets6Select) {
     kea_event_handler_type_.setValue("select");
     testMultipleExternalSockets6();
@@ -3734,8 +4715,6 @@ TEST_F(IfaceMgrTest, unusableExternalSockets6IndirectPoll) {
     unusableExternalSockets6Test(true);
 }
 
-
-
 /// @brief Test fixture for logs.
 class IfaceMgrLogTest : public LogContentTest {
 public:
@@ -3902,691 +4881,196 @@ TEST_F(IfaceMgrTest, DISABLED_getSocket) {
 
     // Let's make sure those sockets are unique
     EXPECT_NE(socket1, socket2);
-    EXPECT_NE(socket2, socket3);
-    EXPECT_NE(socket3, socket1);
-
-    // Create a packet
-    Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 123));
-    pkt6->setIface(LOOPBACK_NAME);
-    pkt6->setIndex(LOOPBACK_INDEX);
-
-    // Check that packets sent to link-local will get socket bound to link local
-    pkt6->setLocalAddr(global);
-    pkt6->setRemoteAddr(dst_global);
-    EXPECT_EQ(socket3, ifacemgr->getSocket(pkt6));
-
-    // Check that packets sent to link-local will get socket bound to link local
-    pkt6->setLocalAddr(link_local);
-    pkt6->setRemoteAddr(dst_link_local);
-    EXPECT_EQ(socket2, ifacemgr->getSocket(pkt6));
-
-    // Close sockets here because the following tests will want to
-    // open sockets on the same ports.
-    ifacemgr->closeSockets();
-}
-
-// Verifies DHCPv4 behavior of configureDHCPPacketQueue()
-TEST_F(IfaceMgrTest, configureDHCPPacketQueueTest4) {
-    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
-
-    // First let's make sure there is no queue and no thread.
-    ASSERT_FALSE(ifacemgr->getPacketQueue4());
-    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
-
-    bool queue_enabled = false;
-    // Given an empty pointer, we should default to no queue.
-    data::ConstElementPtr queue_control;
-    ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control));
-    EXPECT_FALSE(queue_enabled);
-    EXPECT_FALSE(ifacemgr->getPacketQueue4());
-    // configureDHCPPacketQueue() should never start the thread.
-    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
-
-    // Verify that calling startDHCPReceiver with no queue, does NOT start the thread.
-    ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
-    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
-
-    // Now let's try with a populated queue control, but with enable-queue = false.
-    queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, false);
-    ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control));
-    EXPECT_FALSE(queue_enabled);
-    EXPECT_FALSE(ifacemgr->getPacketQueue4());
-    // configureDHCPPacketQueue() should never start the thread.
-    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
-
-    // Now let's enable the queue.
-    queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, true);
-    ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control));
-    ASSERT_TRUE(queue_enabled);
-    // Verify we have correctly created the queue.
-    CHECK_QUEUE_INFO(ifacemgr->getPacketQueue4(), "{ \"capacity\": 500, \"queue-type\": \""
-                     << PacketQueueMgr4::DEFAULT_QUEUE_TYPE4 << "\", \"size\": 0 }");
-    // configureDHCPPacketQueue() should never start the thread.
-    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
-
-    // Calling startDHCPReceiver with a queue, should start the thread.
-    ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
-    ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
-
-    // Verify that calling startDHCPReceiver when the thread is running, throws.
-    ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET), InvalidOperation);
-
-    // Create a disabled config.
-    queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, false);
-
-    // Trying to reconfigure with a running thread should throw.
-    ASSERT_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control),
-                 InvalidOperation);
-
-    // We should still have our queue and the thread should still be running.
-    EXPECT_TRUE(ifacemgr->getPacketQueue4());
-    ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
-
-    // Now let's stop stop the thread.
-    ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver());
-    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
-    // Stopping the thread should not destroy the queue.
-    ASSERT_TRUE(ifacemgr->getPacketQueue4());
-
-    // Reconfigure with the queue turned off.  We should have neither queue nor thread.
-    ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control));
-    EXPECT_FALSE(ifacemgr->getPacketQueue4());
-    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
-}
-
-// Verifies DHCPv6 behavior of configureDHCPPacketQueue()
-TEST_F(IfaceMgrTest, configureDHCPPacketQueueTest6) {
-    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
-
-    // First let's make sure there is no queue and no thread.
-    ASSERT_FALSE(ifacemgr->getPacketQueue6());
-    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
-
-    bool queue_enabled = false;
-    // Given an empty pointer, we should default to no queue.
-    data::ConstElementPtr queue_control;
-    ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control));
-    EXPECT_FALSE(queue_enabled);
-    EXPECT_FALSE(ifacemgr->getPacketQueue6());
-    // configureDHCPPacketQueue() should never start the thread.
-    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
-
-    // Verify that calling startDHCPReceiver with no queue, does NOT start the thread.
-    ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
-    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
-
-    // Now let's try with a populated queue control, but with enable-queue = false.
-    queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, false);
-    ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control));
-    EXPECT_FALSE(queue_enabled);
-    EXPECT_FALSE(ifacemgr->getPacketQueue6());
-    // configureDHCPPacketQueue() should never start the thread.
-    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
-
-    // Now let's enable the queue.
-    queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, true);
-    ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control));
-    ASSERT_TRUE(queue_enabled);
-    // Verify we have correctly created the queue.
-    CHECK_QUEUE_INFO(ifacemgr->getPacketQueue6(), "{ \"capacity\": 500, \"queue-type\": \""
-                     << PacketQueueMgr6::DEFAULT_QUEUE_TYPE6 << "\", \"size\": 0 }");
-    // configureDHCPPacketQueue() should never start the thread.
-    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
-
-    // Calling startDHCPReceiver with a queue, should start the thread.
-    ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6));
-    ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
-
-    // Verify that calling startDHCPReceiver when the thread is running, throws.
-    ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET6), InvalidOperation);
-
-    // Create a disabled config.
-    queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, false);
-
-    // Trying to reconfigure with a running thread should throw.
-    ASSERT_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control),
-                 InvalidOperation);
-
-    // We should still have our queue and the thread should still be running.
-    EXPECT_TRUE(ifacemgr->getPacketQueue6());
-    ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
-
-    // Now let's stop stop the thread.
-    ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver());
-    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
-    // Stopping the thread should not destroy the queue.
-    ASSERT_TRUE(ifacemgr->getPacketQueue6());
-
-    // Reconfigure with the queue turned off.  We should have neither queue nor thread.
-    ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control));
-    EXPECT_FALSE(ifacemgr->getPacketQueue6());
-    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
-}
-
-// Tests that a replace in the SocketCallbackInfoContainer keeps the sequence.
-TEST(SocketCallbackInfoContainer, replace) {
-    IfaceMgr::SocketCallbackInfoContainer callbacks;
-    auto getSequence = [&]() {
-        std::stringstream seq;
-        for (auto const& s : callbacks) {
-            seq << s.socket_ << "\n";
-        }
-        return (seq.str());
-    };
-    for (int i = 0; i < 9; ++i) {
-        IfaceMgr::SocketCallbackInfo s(i);
-        callbacks.push_back(s);
-    }
-    EXPECT_EQ("0\n1\n2\n3\n4\n5\n6\n7\n8\n", getSequence());
-    auto& idx = callbacks.get<1>();
-    auto it = idx.find(5);
-    ASSERT_NE(it, idx.end());
-    IfaceMgr::SocketCallbackInfo x(9);
-    EXPECT_TRUE(idx.replace(it, x));
-    EXPECT_EQ("0\n1\n2\n3\n4\n9\n6\n7\n8\n", getSequence());
-}
-
-const uint8_t MARKER = 0;
-
-class PktFilterIfaceSocketTest {
-public:
-    /// @brief Constructor.
-    ///
-    /// @param ready_on_send Flag which indicates if socket should be marked as
-    /// readReady when calling @ref send.
-    /// @param clear_on_read Flag which indicates is socket should be unmarked as
-    /// readReady when calling @ref receive.
-    PktFilterIfaceSocketTest(bool ready_on_send = true, bool clear_on_read = true);
-
-    /// @brief Destructor.
-    virtual ~PktFilterIfaceSocketTest();
-
-    /// @brief Simulate opening of the socket.
-    ///
-    /// This function simulates opening a primary socket. In reality, it doesn't
-    /// open a socket but uses a pipe which can control if a read event is ready
-    /// or not.
-    ///
-    /// @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).
-    ///
-    /// @return A SocketInfo structure with the socket descriptor set. The
-    /// fallback socket descriptor is set to a negative value.
-    virtual SocketInfo openSocketCommon(const Iface& iface,
-                                        const isc::asiolink::IOAddress& addr,
-                                        const uint16_t port);
-
-    /// @brief Simulate reception of the DHCPv4/DHCPv6 message.
-    ///
-    /// @param sock_info A descriptor of the primary and fallback sockets.
-    ///
-    /// @return the same packet used by @ref send (if any).
-    virtual PktPtr receiveCommon(const SocketInfo& sock_info);
-
-    /// @brief Simulates sending a DHCPv4/DHCPv6 message.
-    ///
-    /// @param iface An interface to be used to send DHCPv4/DHCPv6 message.
-    /// @param sockfd socket descriptor.
-    /// @param pkt A DHCPv4/DHCPv6 to be sent.
-    ///
-    /// @return 0.
-    virtual int sendCommon(const Iface& iface, uint16_t sockfd, const PktPtr& pkt);
-
-    /// @brief Flag which indicates if socket should be marked as
-    /// readReady when calling @ref send.
-    bool ready_on_send_;
-
-    /// @brief Flag which indicates is socket should be unmarked as
-    /// readReady when calling @ref receive.
-    bool clear_on_read_;
+    EXPECT_NE(socket2, socket3);
+    EXPECT_NE(socket3, socket1);
 
-    /// @brief The set of opened file descriptors.
-    std::unordered_map<int, int> socket_fds_;
+    // Create a packet
+    Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 123));
+    pkt6->setIface(LOOPBACK_NAME);
+    pkt6->setIndex(LOOPBACK_INDEX);
 
-    std::unordered_map<int, PktPtr> pkts_;
-};
+    // Check that packets sent to link-local will get socket bound to link local
+    pkt6->setLocalAddr(global);
+    pkt6->setRemoteAddr(dst_global);
+    EXPECT_EQ(socket3, ifacemgr->getSocket(pkt6));
 
-PktFilterIfaceSocketTest::PktFilterIfaceSocketTest(bool ready_on_send, bool clear_on_read)
-    : ready_on_send_(ready_on_send),
-      clear_on_read_(clear_on_read) {
-}
+    // Check that packets sent to link-local will get socket bound to link local
+    pkt6->setLocalAddr(link_local);
+    pkt6->setRemoteAddr(dst_link_local);
+    EXPECT_EQ(socket2, ifacemgr->getSocket(pkt6));
 
-PktFilterIfaceSocketTest::~PktFilterIfaceSocketTest() {
-    for (auto it = socket_fds_.begin(); it != socket_fds_.end(); ++it) {
-        close(it->first);
-        close(it->second);
-    }
+    // Close sockets here because the following tests will want to
+    // open sockets on the same ports.
+    ifacemgr->closeSockets();
 }
 
-SocketInfo
-PktFilterIfaceSocketTest::openSocketCommon(const Iface& /* iface */,
-                                           const isc::asiolink::IOAddress& addr,
-                                           const uint16_t) {
-    int pipe_fds[2];
-    if (pipe(pipe_fds) < 0) {
-        isc_throw(Unexpected, "failed to open test pipe");
-    }
-    if (fcntl(pipe_fds[0], F_SETFL, O_NONBLOCK) < 0) {
-        close(pipe_fds[0]);
-        close(pipe_fds[1]);
-        isc_throw(Unexpected, "fcntl " << strerror(errno));
-    }
-    if (fcntl(pipe_fds[1], F_SETFL, O_NONBLOCK) < 0) {
-        close(pipe_fds[0]);
-        close(pipe_fds[1]);
-        isc_throw(Unexpected, "fcntl " << strerror(errno));
-    }
+// Verifies DHCPv4 behavior of configureDHCPPacketQueue()
+TEST_F(IfaceMgrTest, configureDHCPPacketQueueTest4) {
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
 
-    socket_fds_[pipe_fds[0]] = pipe_fds[1];
-    return (SocketInfo(addr, 9999, pipe_fds[0]));
-}
+    // First let's make sure there is no queue and no thread.
+    ASSERT_FALSE(ifacemgr->getPacketQueue4());
+    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
 
-PktPtr
-PktFilterIfaceSocketTest::receiveCommon(const SocketInfo& s) {
-    auto it = socket_fds_.find(s.sockfd_);
-    if (it == socket_fds_.end()) {
-        std::cout << "receive no such socket: " << s.sockfd_ << std::endl;
-        return (PktPtr());
-    }
-    PktPtr result = pkts_[s.sockfd_];
-    if (clear_on_read_) {
-        uint8_t data;
-        for (size_t count = -1; count; count = read(s.sockfd_, &data, sizeof(data)));
-    }
-    return (result);
-}
+    bool queue_enabled = false;
+    // Given an empty pointer, we should default to no queue.
+    data::ConstElementPtr queue_control;
+    ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control));
+    EXPECT_FALSE(queue_enabled);
+    EXPECT_FALSE(ifacemgr->getPacketQueue4());
+    // configureDHCPPacketQueue() should never start the thread.
+    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
 
-int
-PktFilterIfaceSocketTest::sendCommon(const Iface& /* iface */, uint16_t sockfd, const PktPtr& pkt) {
-    auto it = socket_fds_.find(sockfd);
-    if (it == socket_fds_.end()) {
-        std::cout << "send no such socket: " << sockfd << std::endl;
-        return (-1);
-    }
-    pkts_[sockfd] = pkt;
-    if (ready_on_send_) {
-        uint8_t data = MARKER;
-        write(it->second, &data, sizeof(data));
-    }
-    return (0);
-}
+    // Verify that calling startDHCPReceiver with no queue, does NOT start the thread.
+    ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
+    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
 
-class PktFilter4IfaceSocketTest : public PktFilterIfaceSocketTest, public PktFilter {
-public:
+    // Now let's try with a populated queue control, but with enable-queue = false.
+    queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, false);
+    ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control));
+    EXPECT_FALSE(queue_enabled);
+    EXPECT_FALSE(ifacemgr->getPacketQueue4());
+    // configureDHCPPacketQueue() should never start the thread.
+    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
 
-    /// @brief Constructor.
-    ///
-    /// @param ready_on_send Flag which indicates if socket should be marked as
-    /// readReady when calling @ref send.
-    /// @param clear_on_read Flag which indicates is socket should be unmarked as
-    /// readReady when calling @ref receive.
-    PktFilter4IfaceSocketTest(bool ready_on_send = true, bool clear_on_read = true);
+    // Now let's enable the queue.
+    queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, true);
+    ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control));
+    ASSERT_TRUE(queue_enabled);
+    // Verify we have correctly created the queue.
+    CHECK_QUEUE_INFO(ifacemgr->getPacketQueue4(), "{ \"capacity\": 500, \"queue-type\": \""
+                     << PacketQueueMgr4::DEFAULT_QUEUE_TYPE4 << "\", \"size\": 0 }");
+    // configureDHCPPacketQueue() should never start the thread.
+    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
 
-    /// @brief Destructor.
-    ~PktFilter4IfaceSocketTest() = default;
+    // Calling startDHCPReceiver with a queue, should start the thread.
+    ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
+    ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
 
-    /// @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 always returned.
-    ///
-    /// @return always true.
-    virtual bool isDirectResponseSupported() const;
+    // Verify that calling startDHCPReceiver when the thread is running, throws.
+    ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET), InvalidOperation);
 
-    /// @brief Check if the socket received time is supported.
-    ///
-    /// If true, then packets received through this filter will include
-    /// a SOCKET_RECEIVED event in its event stack.
-    ///
-    /// @return always true.
-    virtual bool isSocketReceivedTimeSupported() const;
+    // Create a disabled config.
+    queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, false);
 
-    /// @brief Simulate opening of the socket.
-    ///
-    /// This function simulates opening a primary socket. In reality, it doesn't
-    /// open a socket but uses a pipe which can control if a read event is ready
-    /// or not.
-    ///
-    /// @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).
-    ///
-    /// @return A SocketInfo structure with the socket descriptor set. The
-    /// fallback socket descriptor is set to a negative value.
-    virtual SocketInfo openSocket(Iface& iface,
-                                  const isc::asiolink::IOAddress& addr,
-                                  const uint16_t port,
-                                  const bool receive_bcast,
-                                  const bool send_bcast);
+    // Trying to reconfigure with a running thread should throw.
+    ASSERT_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control),
+                 InvalidOperation);
 
-    /// @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.
-    ///
-    /// @return the same packet used by @ref send (if any).
-    virtual Pkt4Ptr receive(Iface& iface, const SocketInfo& sock_info);
+    // We should still have our queue and the thread should still be running.
+    EXPECT_TRUE(ifacemgr->getPacketQueue4());
+    ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
 
-    /// @brief Simulates sending a DHCPv4 message.
-    ///
-    /// @param iface An interface to be used to send DHCPv4 message.
-    /// @param sockfd socket descriptor.
-    /// @param pkt A DHCPv4 to be sent.
-    ///
-    /// @return 0.
-    virtual int send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt);
-};
+    // Now let's stop stop the thread.
+    ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver());
+    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+    // Stopping the thread should not destroy the queue.
+    ASSERT_TRUE(ifacemgr->getPacketQueue4());
 
-PktFilter4IfaceSocketTest::PktFilter4IfaceSocketTest(bool ready_on_send, bool clear_on_read)
-    : PktFilterIfaceSocketTest(ready_on_send, clear_on_read) {
+    // Reconfigure with the queue turned off.  We should have neither queue nor thread.
+    ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control));
+    EXPECT_FALSE(ifacemgr->getPacketQueue4());
+    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
 }
 
-bool
-PktFilter4IfaceSocketTest::isDirectResponseSupported() const {
-    return (true);
-}
+// Verifies DHCPv6 behavior of configureDHCPPacketQueue()
+TEST_F(IfaceMgrTest, configureDHCPPacketQueueTest6) {
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
 
-bool
-PktFilter4IfaceSocketTest::isSocketReceivedTimeSupported() const {
-    return (true);
-}
+    // First let's make sure there is no queue and no thread.
+    ASSERT_FALSE(ifacemgr->getPacketQueue6());
+    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
 
-SocketInfo
-PktFilter4IfaceSocketTest::openSocket(Iface& iface,
-                                      const isc::asiolink::IOAddress& addr,
-                                      const uint16_t port, const bool, const bool) {
-    return (PktFilterIfaceSocketTest::openSocketCommon(iface, addr, port));
-}
+    bool queue_enabled = false;
+    // Given an empty pointer, we should default to no queue.
+    data::ConstElementPtr queue_control;
+    ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET, queue_control));
+    EXPECT_FALSE(queue_enabled);
+    EXPECT_FALSE(ifacemgr->getPacketQueue6());
+    // configureDHCPPacketQueue() should never start the thread.
+    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
 
-Pkt4Ptr
-PktFilter4IfaceSocketTest::receive(Iface& /* iface */, const SocketInfo& s) {
-    return (boost::dynamic_pointer_cast<Pkt4>(PktFilterIfaceSocketTest::receiveCommon(s)));
-}
+    // Verify that calling startDHCPReceiver with no queue, does NOT start the thread.
+    ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET));
+    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
 
-int
-PktFilter4IfaceSocketTest::send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt) {
-    return (PktFilterIfaceSocketTest::sendCommon(iface, sockfd, pkt));
-}
+    // Now let's try with a populated queue control, but with enable-queue = false.
+    queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, false);
+    ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control));
+    EXPECT_FALSE(queue_enabled);
+    EXPECT_FALSE(ifacemgr->getPacketQueue6());
+    // configureDHCPPacketQueue() should never start the thread.
+    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
 
-class PktFilter6IfaceSocketTest : public PktFilterIfaceSocketTest, public PktFilter6 {
-public:
+    // Now let's enable the queue.
+    queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, true);
+    ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control));
+    ASSERT_TRUE(queue_enabled);
+    // Verify we have correctly created the queue.
+    CHECK_QUEUE_INFO(ifacemgr->getPacketQueue6(), "{ \"capacity\": 500, \"queue-type\": \""
+                     << PacketQueueMgr6::DEFAULT_QUEUE_TYPE6 << "\", \"size\": 0 }");
+    // configureDHCPPacketQueue() should never start the thread.
+    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+
+    // Calling startDHCPReceiver with a queue, should start the thread.
+    ASSERT_NO_THROW(ifacemgr->startDHCPReceiver(AF_INET6));
+    ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
 
-    /// @brief Constructor.
-    ///
-    /// @param ready_on_send Flag which indicates if socket should be marked as
-    /// readReady when calling @ref send.
-    /// @param clear_on_read Flag which indicates is socket should be unmarked as
-    /// readReady when calling @ref receive.
-    PktFilter6IfaceSocketTest(bool ready_on_send = true, bool clear_on_read = true);
+    // Verify that calling startDHCPReceiver when the thread is running, throws.
+    ASSERT_THROW(ifacemgr->startDHCPReceiver(AF_INET6), InvalidOperation);
 
-    /// @brief Destructor.
-    ~PktFilter6IfaceSocketTest() = default;
+    // Create a disabled config.
+    queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, false);
 
-    /// @brief Simulate opening of the socket.
-    ///
-    /// This function simulates opening a primary socket. In reality, it doesn't
-    /// open a socket but uses a pipe which can control if a read event is ready
-    /// or not.
-    ///
-    /// @param iface Interface descriptor.
-    /// @param addr Address on the interface to be used to send packets.
-    /// @param port Port number.
-    /// @param join_multicast A boolean parameter which indicates whether
-    /// socket should join All_DHCP_Relay_Agents_and_servers multicast
-    /// group.
-    ///
-    /// @return A SocketInfo structure with the socket descriptor set. 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 join_multicast);
+    // Trying to reconfigure with a running thread should throw.
+    ASSERT_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control),
+                 InvalidOperation);
 
-    /// @brief Simulate reception of the DHCPv6 message.
-    ///
-    /// @param iface An interface to be used to receive DHCPv6 message.
-    /// @param sock_info A descriptor of the primary and fallback sockets.
-    ///
-    /// @return the same packet used by @ref send (if any).
-    virtual Pkt6Ptr receive(const SocketInfo& sock_info);
+    // We should still have our queue and the thread should still be running.
+    EXPECT_TRUE(ifacemgr->getPacketQueue6());
+    ASSERT_TRUE(ifacemgr->isDHCPReceiverRunning());
 
-    /// @brief Simulates sending a DHCPv6 message.
-    ///
-    /// @param iface An interface to be used to send DHCPv6 message.
-    /// @param sockfd socket descriptor.
-    /// @param pkt A DHCPv6 to be sent.
-    ///
-    /// @return 0.
-    virtual int send(const Iface& iface, uint16_t sockfd, const Pkt6Ptr& pkt);
-};
+    // Now let's stop stop the thread.
+    ASSERT_NO_THROW(ifacemgr->stopDHCPReceiver());
+    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
+    // Stopping the thread should not destroy the queue.
+    ASSERT_TRUE(ifacemgr->getPacketQueue6());
 
-PktFilter6IfaceSocketTest::PktFilter6IfaceSocketTest(bool ready_on_send, bool clear_on_read)
-    : PktFilterIfaceSocketTest(ready_on_send, clear_on_read) {
+    // Reconfigure with the queue turned off.  We should have neither queue nor thread.
+    ASSERT_NO_THROW(queue_enabled = ifacemgr->configureDHCPPacketQueue(AF_INET6, queue_control));
+    EXPECT_FALSE(ifacemgr->getPacketQueue6());
+    ASSERT_FALSE(ifacemgr->isDHCPReceiverRunning());
 }
 
-SocketInfo
-PktFilter6IfaceSocketTest::openSocket(const Iface& iface,
-                                      const isc::asiolink::IOAddress& addr,
-                                      const uint16_t port, const bool) {
-    return (PktFilterIfaceSocketTest::openSocketCommon(iface, addr, port));
+TEST_F(IfaceMgrTest, directReceive4RotateAll) {
+    testReceive4RotateAll();
 }
 
-Pkt6Ptr
-PktFilter6IfaceSocketTest::receive(const SocketInfo& s) {
-    return (boost::dynamic_pointer_cast<Pkt6>(PktFilterIfaceSocketTest::receiveCommon(s)));
+TEST_F(IfaceMgrTest, indirectReceive4RotateAll) {
+    testReceive4RotateAll(false);
 }
 
-int
-PktFilter6IfaceSocketTest::send(const Iface& iface, uint16_t sockfd, const Pkt6Ptr& pkt) {
-    return (PktFilterIfaceSocketTest::sendCommon(iface, sockfd, pkt));
+TEST_F(IfaceMgrTest, directReceive6RotateAll) {
+    testReceive6RotateAll();
 }
 
-/// Ported tests (for bench add " * 1024" to loop_count definitions.
-
-typedef boost::hash<Pkt4Ptr> hash_pkt4;
-typedef boost::hash<Pkt6Ptr> hash_pkt6;
-
-class IfaceMgrRBTest : public IfaceMgrTest {
-public:
-    ~IfaceMgrRBTest() {
-        IfaceMgr::instance().stopDHCPReceiver();
-        IfaceMgr::instance().clearIfaces();
-        IfaceMgr::instance().deleteAllExternalSockets();
-        IfaceMgr::instance().detectIfaces();
-        IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet()));
-        IfaceMgr::instance().setPacketFilter(PktFilter6Ptr(new PktFilterInet6()));
-        IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, data::ConstElementPtr());
-        IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, data::ConstElementPtr());
-    }
-
-    /// @brief Test that iface sockets are rotated when ifaces are under load.
-    ///
-    /// @param direct Flag which indicates if direct or indirect receive should
-    /// be used.
-    void testReceive4RotateIfaces(bool direct = true) {
-        const size_t loop_count = 1024;
-        IfaceMgr::instance().clearIfaces();
-        IfaceMgr::instance().deleteAllExternalSockets();
-        PktFilterPtr filter(new PktFilter4IfaceSocketTest(true, false));
-        IfaceMgr::instance().setPacketFilter(filter);
-        IfacePtr iface0(new Iface("eth0", 0));
-        iface0->flag_up_ = true;
-        iface0->flag_running_ = true;
-        iface0->addAddress(IOAddress("192.168.0.1"));
-        IfaceMgr::instance().addInterface(iface0);
-        IfacePtr iface1(new Iface("eth1", 1));
-        iface1->flag_up_ = true;
-        iface1->flag_running_ = true;
-        iface1->addAddress(IOAddress("192.168.0.2"));
-        IfaceMgr::instance().addInterface(iface1);
-        IfacePtr iface2(new Iface("eth2", 2));
-        iface2->flag_up_ = true;
-        iface2->flag_running_ = true;
-        iface2->addAddress(IOAddress("192.168.0.3"));
-        IfaceMgr::instance().addInterface(iface2);
-        for (size_t i = 3; i < 250; ++i) {
-            string name = "eth";
-            name += std::to_string(i);
-            IfacePtr iface_n(new Iface(name, i));
-            iface_n->flag_up_ = true;
-            iface_n->flag_running_ = true;
-            iface_n->addAddress(IOAddress(string("192.168.0.") + std::to_string(i)));
-            IfaceMgr::instance().addInterface(iface_n);
-        }
-        if (!direct) {
-            auto queue_control = makeQueueConfig(PacketQueueMgr4::DEFAULT_QUEUE_TYPE4, 500, true);
-            IfaceMgr::instance().configureDHCPPacketQueue(AF_INET, queue_control);
-        } else {
-            IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, data::ConstElementPtr());
-        }
-        IfaceMgr::instance().openSockets4(9999, true, IfaceMgrErrorMsgCallback(), false);
-        EXPECT_EQ((boost::dynamic_pointer_cast<PktFilter4IfaceSocketTest>(filter))->socket_fds_.size(), 250);
-        Pkt4Ptr pkt0(new Pkt4(DHCPDISCOVER, 1234));
-        pkt0->setIface("eth0");
-        pkt0->setIndex(0);
-        Pkt4Ptr pkt1(new Pkt4(DHCPDISCOVER, 2345));
-        pkt1->setIface("eth1");
-        pkt1->setIndex(1);
-        Pkt4Ptr pkt2(new Pkt4(DHCPDISCOVER, 3456));
-        pkt2->setIface("eth2");
-        pkt2->setIndex(2);
-        IfaceMgr::instance().send(pkt0);
-        IfaceMgr::instance().send(pkt1);
-        IfaceMgr::instance().send(pkt2);
-        std::unordered_map<Pkt4Ptr, size_t, hash_pkt4> expected;
-        expected[pkt0] = 0;
-        expected[pkt1] = 0;
-        expected[pkt2] = 0;
-        for (size_t i = 0, j = 0; i < 3 * loop_count && j < 2048;) {
-            Pkt4Ptr new_pkt = IfaceMgr::instance().receive4(1, 0);
-            if (new_pkt) {
-                expected[new_pkt]++;
-                if (i % 3 == 0) {
-                    EXPECT_EQ(new_pkt.get(), pkt0.get());
-                } else if (i % 3 == 1) {
-                    EXPECT_EQ(new_pkt.get(), pkt1.get());
-                } else {
-                    EXPECT_EQ(new_pkt.get(), pkt2.get());
-                }
-                i++;
-            } else {
-                j++;
-            }
-        }
-        for (auto i = expected.begin(); i!= expected.end(); ++i) {
-            EXPECT_EQ(loop_count, i->second);
-        }
-        ASSERT_NO_THROW(IfaceMgr::instance().stopDHCPReceiver());
-    }
-
-    /// @brief Test that iface sockets are rotated when ifaces are under load.
-    ///
-    /// @param direct Flag which indicates if direct or indirect receive should
-    /// be used.
-    void testReceive6RotateIfaces(bool direct = true) {
-        const size_t loop_count = 1024;
-        IfaceMgr::instance().clearIfaces();
-        IfaceMgr::instance().deleteAllExternalSockets();
-        PktFilter6Ptr filter(new PktFilter6IfaceSocketTest(true, false));
-        IfaceMgr::instance().setPacketFilter(filter);
-        IfacePtr iface0(new Iface("eth0", 0));
-        iface0->flag_up_ = true;
-        iface0->flag_running_ = true;
-        iface0->addAddress(IOAddress("2003:db8::1"));
-        iface0->addAddress(IOAddress("fe80::3a60:77ff:fed5:abcd"));
-        IfaceMgr::instance().addInterface(iface0);
-        IfacePtr iface1(new Iface("eth1", 1));
-        iface1->flag_up_ = true;
-        iface1->flag_running_ = true;
-        iface1->addAddress(IOAddress("2003:db8::2"));
-        iface1->addAddress(IOAddress("fe80::3a60:77ff:fed5:bcde"));
-        IfaceMgr::instance().addInterface(iface1);
-        IfacePtr iface2(new Iface("eth2", 2));
-        iface2->flag_up_ = true;
-        iface2->flag_running_ = true;
-        iface2->addAddress(IOAddress("2003:db8::3"));
-        iface2->addAddress(IOAddress("fe80::3a60:77ff:fed5:cdef"));
-        IfaceMgr::instance().addInterface(iface2);
-        for (size_t i = 3; i < 250; ++i) {
-            string name = "eth";
-            name += std::to_string(i);
-            IfacePtr iface_n(new Iface(name, i));
-            iface_n->flag_up_ = true;
-            iface_n->flag_running_ = true;
-            iface_n->addAddress(IOAddress(string("2003:db8::") + std::to_string(i)));
-            iface_n->addAddress(IOAddress(string("fe80::3a60:77ff:fed5:") + std::to_string(i)));
-            IfaceMgr::instance().addInterface(iface_n);
-        }
-        if (!direct) {
-            auto queue_control = makeQueueConfig(PacketQueueMgr6::DEFAULT_QUEUE_TYPE6, 500, true);
-            IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, queue_control);
-        } else {
-            IfaceMgr::instance().configureDHCPPacketQueue(AF_INET6, data::ConstElementPtr());
-        }
-        IfaceMgr::instance().openSockets6(9999, IfaceMgrErrorMsgCallback(), false);
-        EXPECT_EQ((boost::dynamic_pointer_cast<PktFilter6IfaceSocketTest>(filter))->socket_fds_.size(), 250);
-        Pkt6Ptr pkt0(new Pkt6(DHCPV6_SOLICIT, 1234));
-        pkt0->setIface("eth0");
-        pkt0->setIndex(0);
-        Pkt6Ptr pkt1(new Pkt6(DHCPV6_SOLICIT, 2345));
-        pkt1->setIface("eth1");
-        pkt1->setIndex(1);
-        Pkt6Ptr pkt2(new Pkt6(DHCPV6_SOLICIT, 3456));
-        pkt2->setIface("eth2");
-        pkt2->setIndex(2);
-        IfaceMgr::instance().send(pkt0);
-        IfaceMgr::instance().send(pkt1);
-        IfaceMgr::instance().send(pkt2);
-        std::unordered_map<Pkt6Ptr, size_t, hash_pkt6> expected;
-        expected[pkt0] = 0;
-        expected[pkt1] = 0;
-        expected[pkt2] = 0;
-        for (size_t i = 0, j = 0; i < 3 * loop_count && j < 2048;) {
-            Pkt6Ptr new_pkt = IfaceMgr::instance().receive6(1, 0);
-            if (new_pkt) {
-                expected[new_pkt]++;
-                if (i % 3 == 0) {
-                    EXPECT_EQ(new_pkt.get(), pkt0.get());
-                } else if (i % 3 == 1) {
-                    EXPECT_EQ(new_pkt.get(), pkt1.get());
-                } else {
-                    EXPECT_EQ(new_pkt.get(), pkt2.get());
-                }
-                i++;
-            } else {
-                j++;
-            }
-        }
-        for (auto i = expected.begin(); i!= expected.end(); ++i) {
-            EXPECT_EQ(loop_count, i->second);
-        }
-        ASSERT_NO_THROW(IfaceMgr::instance().stopDHCPReceiver());
-    }
-};
+TEST_F(IfaceMgrTest, indirectReceive6RotateAll) {
+    testReceive6RotateAll(false);
+}
 
-TEST_F(IfaceMgrRBTest, directReceive4RotateIfaces) {
+TEST_F(IfaceMgrTest, directReceive4RotateIfaces) {
     testReceive4RotateIfaces();
 }
 
-TEST_F(IfaceMgrRBTest, indirectReceive4RotateIfaces) {
+TEST_F(IfaceMgrTest, indirectReceive4RotateIfaces) {
     testReceive4RotateIfaces(false);
 }
 
-TEST_F(IfaceMgrRBTest, directReceive6RotateIfaces) {
+TEST_F(IfaceMgrTest, directReceive6RotateIfaces) {
     testReceive6RotateIfaces();
 }
 
-TEST_F(IfaceMgrRBTest, indirectReceive6RotateIfaces) {
+TEST_F(IfaceMgrTest, indirectReceive6RotateIfaces) {
     testReceive6RotateIfaces(false);
 }