From: Razvan Becheriu Date: Thu, 15 Jan 2026 13:33:14 +0000 (+0200) Subject: [#4212] added more UTs X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;ds=sidebyside;p=thirdparty%2Fkea.git [#4212] added more UTs --- diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc index f764cdf106..58f82b516f 100644 --- a/src/lib/dhcp/iface_mgr.cc +++ b/src/lib/dhcp/iface_mgr.cc @@ -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 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 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 lk(receiver_mutex_); - dhcp_receiver_->setError(strerror(errno)); + dhcp_receiver_->setError(ex.what()); } catch (...) { std::lock_guard lk(receiver_mutex_); dhcp_receiver_->setError("packet filter receive() failed"); diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc index 25835ad841..63a3f86a3d 100644 --- a/src/lib/dhcp/tests/iface_mgr_unittest.cc +++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc @@ -33,6 +33,8 @@ #include #include +#include +#include #include using namespace std; @@ -47,6 +49,10 @@ namespace ph = std::placeholders; namespace { +typedef boost::hash hash_pkt4; + +typedef boost::hash 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 socket_fds_; -void my_callback2(int /* fd */) { - callback2_ok = true; -} + std::unordered_map 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 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 - // receiveDHCPPackets() 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 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 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(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 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(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 - // receiveDHCPPackets() 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 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 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 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 + // receiveDHCPPackets() 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 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 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 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 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 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 + // receiveDHCPPackets() 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 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 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 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 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 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 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(remove("interfaces.txt")); + /// Check the order before the first call to receive6. + std::list 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 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(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 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::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(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 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(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 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(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 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(remove("interfaces.txt")); + + ofstream interfaces("interfaces.txt", ios::ate); + interfaces << "eth0 fe80::21e:8cff:fe9b:7349"; + interfaces.close(); + + boost::scoped_ptr 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::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 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 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 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 socket_fds_; + // Create a packet + Pkt6Ptr pkt6(new Pkt6(DHCPV6_SOLICIT, 123)); + pkt6->setIface(LOOPBACK_NAME); + pkt6->setIndex(LOOPBACK_INDEX); - std::unordered_map 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 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 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(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(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 hash_pkt4; -typedef boost::hash 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(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 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(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 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); }