diff options
Diffstat (limited to 'src/bin/dhcp4/tests/dhcp4to6_ipc_unittest.cc')
-rw-r--r-- | src/bin/dhcp4/tests/dhcp4to6_ipc_unittest.cc | 407 |
1 files changed, 407 insertions, 0 deletions
diff --git a/src/bin/dhcp4/tests/dhcp4to6_ipc_unittest.cc b/src/bin/dhcp4/tests/dhcp4to6_ipc_unittest.cc new file mode 100644 index 0000000..6cd5104 --- /dev/null +++ b/src/bin/dhcp4/tests/dhcp4to6_ipc_unittest.cc @@ -0,0 +1,407 @@ +// Copyright (C) 2015-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/io_address.h> +#include <dhcp/pkt4o6.h> +#include <dhcp/pkt6.h> +#include <dhcp/testutils/iface_mgr_test_config.h> +#include <dhcp4/ctrl_dhcp4_srv.h> +#include <dhcp4/dhcp4to6_ipc.h> +#include <dhcp4/tests/dhcp4_test_utils.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/testutils/dhcp4o6_test_ipc.h> +#include <stats/stats_mgr.h> +#include <hooks/callout_handle.h> +#include <hooks/hooks_manager.h> + +#include <gtest/gtest.h> +#include <stdint.h> +#include <utility> + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::dhcp::test; +using namespace isc::stats; +using namespace isc::hooks; +using namespace isc::util; + +namespace { + +/// @brief Port number used in tests. +const uint16_t TEST_PORT = 32000; + +/// @brief Define short name for the test IPC. +typedef Dhcp4o6TestIpc TestIpc; + +/// @brief Test fixture class for DHCPv4 endpoint of DHCPv4o6 IPC. +class Dhcp4to6IpcTest : public Dhcpv4SrvTest { +public: + + /// @brief Constructor + /// + /// Configures IPC to use a test port. It also provides a fake + /// configuration of interfaces. + Dhcp4to6IpcTest() + : Dhcpv4SrvTest(), + iface_mgr_test_config_(true) { + IfaceMgr::instance().openSockets4(); + configurePort(TEST_PORT); + // Install buffer4_receive_callout + EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle(). + registerCallout("buffer4_receive", + buffer4_receive_callout)); + // Install buffer4_send_callout + EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle(). + registerCallout("buffer4_send", buffer4_send_callout)); + // Verify we have a controlled server + ControlledDhcpv4Srv* srv = NULL; + EXPECT_NO_THROW(srv = ControlledDhcpv4Srv::getInstance()); + EXPECT_TRUE(srv); + // Let's wipe all existing statistics. + StatsMgr::instance().removeAll(); + + // Set the flags to false as we expect them to be set in callouts. + callback_recv_pkt_options_copy_ = std::make_pair(false, false); + callback_sent_pkt_options_copy_ = std::make_pair(false, false); + } + + /// @brief Destructor + /// + /// Various cleanups. + virtual ~Dhcp4to6IpcTest() { + HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer4_send"); + callback_recv_pkt_.reset(); + callback_sent_pkt_.reset(); + bool status = HooksManager::unloadLibraries(); + if (!status) { + cerr << "(fixture dtor) unloadLibraries failed" << endl; + } + } + + /// @brief Configure DHCP4o6 port. + /// + /// @param port New port. + void configurePort(uint16_t port); + + /// @brief Creates an instance of the DHCPv4o6 Message option. + /// + /// The option will contain an empty DHCPREQUEST message, with + /// just the Message Type option inside and nothing else. + /// + /// @return Pointer to the instance of the DHCPv4-query Message option. + OptionPtr createDHCPv4MsgOption() const; + + /// @brief Handler for the buffer4_receive hook + /// + /// This hook is at the beginning of processPacket + /// + /// @param callout_handle handle passed by the hooks framework + /// @return always 0 + static int buffer4_receive_callout(CalloutHandle& callout_handle) { + callout_handle.getArgument("query4", callback_recv_pkt_); + Pkt4o6Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4o6>(callback_recv_pkt_); + if (pkt4) { + callback_recv_pkt_options_copy_.first = pkt4->isCopyRetrievedOptions(); + Pkt6Ptr pkt6 = pkt4->getPkt6(); + if (pkt6) { + callback_recv_pkt_options_copy_.second = + pkt6->isCopyRetrievedOptions(); + } + } + return (0); + } + + /// @brief Handler for the buffer4_send hook + /// + /// This hook is at the end of the DHCPv4o6 packet handler + /// + /// @param callout_handle handle passed by the hooks framework + /// @return always 0 + static int buffer4_send_callout(CalloutHandle& callout_handle) { + callout_handle.getArgument("response4", callback_sent_pkt_); + Pkt4o6Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4o6>(callback_sent_pkt_); + if (pkt4) { + callback_sent_pkt_options_copy_.first = pkt4->isCopyRetrievedOptions(); + Pkt6Ptr pkt6 = pkt4->getPkt6(); + if (pkt6) { + callback_sent_pkt_options_copy_.second = + pkt6->isCopyRetrievedOptions(); + } + } + return (0); + } + + /// @brief Response Pkt4 shared pointer returned in the receive callout + static Pkt4Ptr callback_recv_pkt_; + + /// @brief Response Pkt4 shared pointer returned in the send callout + static Pkt4Ptr callback_sent_pkt_; + + /// Flags indicating if copying retrieved options was enabled for + /// a received packet during callout execution. + static std::pair<bool, bool> callback_recv_pkt_options_copy_; + + /// Flags indicating if copying retrieved options was enabled for + /// a sent packet during callout execution. + static std::pair<bool, bool> callback_sent_pkt_options_copy_; + + /// @brief reference to a controlled server + /// + /// Dhcp4to6Ipc::handler() uses the instance of the controlled server + /// so it has to be build. This reference does this. + ControlledDhcpv4Srv srv_; + +private: + + /// @brief Provides fake configuration of interfaces. + IfaceMgrTestConfig iface_mgr_test_config_; + +}; + +Pkt4Ptr Dhcp4to6IpcTest::callback_recv_pkt_; +Pkt4Ptr Dhcp4to6IpcTest::callback_sent_pkt_; +std::pair<bool, bool> Dhcp4to6IpcTest::callback_recv_pkt_options_copy_; +std::pair<bool, bool> Dhcp4to6IpcTest::callback_sent_pkt_options_copy_; + +void +Dhcp4to6IpcTest::configurePort(uint16_t port) { + CfgMgr::instance().getStagingCfg()->setDhcp4o6Port(port); +} + +OptionPtr +Dhcp4to6IpcTest::createDHCPv4MsgOption() const { + // Create the DHCPv4 message. + Pkt4Ptr pkt(new Pkt4(DHCPREQUEST, 1234)); + // Make a wire representation of the DHCPv4 message. + pkt->pack(); + const OptionBuffer& option_buffer = pkt->getBuffer().getVector(); + + // Create the DHCPv4 Message option holding the created message. + OptionPtr opt_msg(new Option(Option::V6, D6O_DHCPV4_MSG, option_buffer)); + return (opt_msg); +} + +// This test verifies that the IPC returns an error when trying to bind +// to the out of range port. +TEST_F(Dhcp4to6IpcTest, invalidPortError) { + // Create instance of the IPC endpoint under test with out-of-range port. + configurePort(65535); + Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance(); + EXPECT_THROW(ipc.open(), isc::OutOfRange); +} + +// This test verifies that the DHCPv4 endpoint of the DHCPv4o6 IPC can +// receive messages. +TEST_F(Dhcp4to6IpcTest, receive) { + // Create instance of the IPC endpoint under test. + Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance(); + // Create instance of the IPC endpoint being used as a source of messages. + TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6); + + // Open both endpoints. + ASSERT_NO_THROW(ipc.open()); + ASSERT_NO_THROW(src_ipc.open()); + + // Create message to be sent over IPC. + Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 1234)); + pkt->addOption(createDHCPv4MsgOption()); + pkt->setIface("eth0"); + pkt->setIndex(ETH0_INDEX); + pkt->setRemoteAddr(IOAddress("2001:db8:1::123")); + ASSERT_NO_THROW(pkt->pack()); + + // Reset the received packet + Dhcp4to6IpcTest::callback_recv_pkt_.reset(); + + // Send and wait up to 1 second to receive it. + ASSERT_NO_THROW(src_ipc.send(pkt)); + ASSERT_NO_THROW(IfaceMgr::instance().receive6(1, 0)); + + // Make sure that the message has been received. + // The buffer4_receive hook is at the beginning of processPacket + // so this proves it was passed to it. + Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_; + ASSERT_TRUE(pkt4_received); + Pkt4o6Ptr pkt_received = + boost::dynamic_pointer_cast<Pkt4o6>(pkt4_received); + ASSERT_TRUE(pkt_received); + Pkt6Ptr pkt6_received = pkt_received->getPkt6(); + ASSERT_TRUE(pkt6_received); + EXPECT_EQ("eth0", pkt6_received->getIface()); + EXPECT_EQ(ETH0_INDEX, pkt6_received->getIndex()); + EXPECT_EQ("2001:db8:1::123", pkt6_received->getRemoteAddr().toText()); + + // Both DHCP4o6 and encapsulated DHCPv6 packet should have the + // flag enabled. + EXPECT_TRUE(callback_recv_pkt_options_copy_.first); + EXPECT_TRUE(callback_recv_pkt_options_copy_.second); +} + +// This test verifies that message with multiple DHCPv4 query options +// is rejected. +TEST_F(Dhcp4to6IpcTest, receiveMultipleQueries) { + // Create instance of the IPC endpoint under test. + Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance(); + // Create instance of the IPC endpoint being used as a source of messages. + TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6); + + // Open both endpoints. + ASSERT_NO_THROW(ipc.open()); + ASSERT_NO_THROW(src_ipc.open()); + + // Create message to be sent over IPC. + Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 1234)); + // Add two DHCPv4 query options. + pkt->addOption(createDHCPv4MsgOption()); + pkt->addOption(createDHCPv4MsgOption()); + pkt->setIface("eth0"); + pkt->setIndex(ETH0_INDEX); + pkt->setRemoteAddr(IOAddress("2001:db8:1::123")); + ASSERT_NO_THROW(pkt->pack()); + + // Reset the received packet + Dhcp4to6IpcTest::callback_recv_pkt_.reset(); + + // Send and wait up to 1 second to receive it. + ASSERT_NO_THROW(src_ipc.send(pkt)); + EXPECT_NO_THROW(IfaceMgr::instance().receive6(1, 0)); + + // No message should has been sent. + Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_; + EXPECT_FALSE(pkt4_received); +} + +// This test verifies that message with no DHCPv4 query options is rejected. +TEST_F(Dhcp4to6IpcTest, receiveNoQueries) { + // Create instance of the IPC endpoint under test. + Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance(); + // Create instance of the IPC endpoint being used as a source of messages. + TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6); + + // Open both endpoints. + ASSERT_NO_THROW(ipc.open()); + ASSERT_NO_THROW(src_ipc.open()); + + // Create message to be sent over IPC without DHCPv4 query option. + Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 1234)); + pkt->setIface("eth0"); + pkt->setIndex(ETH0_INDEX); + pkt->setRemoteAddr(IOAddress("2001:db8:1::123")); + ASSERT_NO_THROW(pkt->pack()); + + // Reset the received packet + Dhcp4to6IpcTest::callback_recv_pkt_.reset(); + + // Send and wait up to 1 second to receive it. + ASSERT_NO_THROW(src_ipc.send(pkt)); + EXPECT_NO_THROW(IfaceMgr::instance().receive6(1, 0)); + + // No message should has been sent. + Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_; + EXPECT_FALSE(pkt4_received); +} + +// This test verifies that the DHCPv4 endpoint of the DHCPv4o6 IPC can +// process messages. +TEST_F(Dhcp4to6IpcTest, process) { + // Create instance of the IPC endpoint under test. + Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance(); + // Create instance of the IPC endpoint being used as a source of messages. + TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6); + + // Open both endpoints. + ASSERT_NO_THROW(ipc.open()); + ASSERT_NO_THROW(src_ipc.open()); + + // Get statistics + StatsMgr& mgr = StatsMgr::instance(); + ObservationPtr pkt4_snd = mgr.getObservation("pkt4-sent"); + ObservationPtr pkt4_ack = mgr.getObservation("pkt4-ack-sent"); + EXPECT_FALSE(pkt4_snd); + EXPECT_FALSE(pkt4_ack); + + // Create an information request message + Pkt4Ptr infreq(new Pkt4(DHCPINFORM, 1234)); + infreq->setHWAddr(generateHWAddr(6)); + infreq->setCiaddr(IOAddress("192.0.1.2")); + // Make a wire representation of the DHCPv4 message. + infreq->pack(); + const OptionBuffer& option_buffer = infreq->getBuffer().getVector(); + + // Create the DHCPv4 Message option holding the created message. + OptionPtr opt_msg(new Option(Option::V6, D6O_DHCPV4_MSG, option_buffer)); + + // Create message to be sent over IPC. + Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 1234)); + pkt->addOption(opt_msg); + pkt->setIface("eth0"); + pkt->setIndex(ETH0_INDEX); + pkt->setRemoteAddr(IOAddress("2001:db8:1::123")); + ASSERT_NO_THROW(pkt->pack()); + + // Reset the received packet + Dhcp4to6IpcTest::callback_recv_pkt_.reset(); + + // Send and wait up to 1 second to receive it. + ASSERT_NO_THROW(src_ipc.send(pkt)); + ASSERT_NO_THROW(IfaceMgr::instance().receive6(1, 0)); + + // Make sure that the message has been received. + Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_; + ASSERT_TRUE(pkt4_received); + Pkt4o6Ptr pkt_received = + boost::dynamic_pointer_cast<Pkt4o6>(pkt4_received); + ASSERT_TRUE(pkt_received); + Pkt6Ptr pkt6_received = pkt_received->getPkt6(); + ASSERT_TRUE(pkt6_received); + EXPECT_EQ("eth0", pkt6_received->getIface()); + EXPECT_EQ(ETH0_INDEX, pkt6_received->getIndex()); + EXPECT_EQ("2001:db8:1::123", pkt6_received->getRemoteAddr().toText()); + + // Make sure that the message has been processed. + // Using the buffer4_send hook + Pkt4Ptr pkt4_sent = Dhcp4to6IpcTest::callback_sent_pkt_; + ASSERT_TRUE(pkt4_sent); + EXPECT_EQ(DHCPACK, pkt4_sent->getType()); + Pkt4o6Ptr pkt_sent = boost::dynamic_pointer_cast<Pkt4o6>(pkt4_sent); + ASSERT_TRUE(pkt_sent); + Pkt6Ptr pkt6_sent = pkt_sent->getPkt6(); + ASSERT_TRUE(pkt6_sent); + EXPECT_EQ(DHCPV6_DHCPV4_RESPONSE, pkt6_sent->getType()); + EXPECT_EQ("eth0", pkt6_sent->getIface()); + EXPECT_EQ(ETH0_INDEX, pkt6_sent->getIndex()); + EXPECT_EQ("2001:db8:1::123", pkt6_sent->getRemoteAddr().toText()); + + // Both DHCP4o6 and encapsulated DHCPv6 packet should have the + // flag enabled. + EXPECT_TRUE(callback_sent_pkt_options_copy_.first); + EXPECT_TRUE(callback_sent_pkt_options_copy_.second); + + // Verify the 4o6 part + OptionCollection sent_msgs = pkt6_sent->getOptions(D6O_DHCPV4_MSG); + ASSERT_EQ(1, sent_msgs.size()); + OptionPtr sent_msg = sent_msgs.begin()->second; + ASSERT_TRUE(sent_msg); + const OptionBuffer sent_buf = sent_msg->getData(); + Pkt4Ptr pkt4_opt; + ASSERT_NO_THROW(pkt4_opt.reset(new Pkt4(&sent_buf[0], sent_buf.size()))); + ASSERT_NO_THROW(pkt4_opt->unpack()); + EXPECT_EQ(DHCPACK, pkt4_sent->getType()); + EXPECT_EQ(pkt4_sent->len(), pkt4_opt->len()); + + // Verify statistics + pkt4_snd = mgr.getObservation("pkt4-sent"); + pkt4_ack = mgr.getObservation("pkt4-ack-sent"); + ASSERT_TRUE(pkt4_snd); + ASSERT_TRUE(pkt4_ack); + EXPECT_EQ(1, pkt4_snd->getInteger().first); + EXPECT_EQ(1, pkt4_ack->getInteger().first); +} + +} // end of anonymous namespace |