diff options
-rw-r--r-- | src/bin/dhcp4/ctrl_dhcp4_srv.cc | 2 | ||||
-rw-r--r-- | src/bin/dhcp4/dhcp4_dhcp4o6_ipc.cc | 47 | ||||
-rw-r--r-- | src/bin/dhcp4/dhcp4_dhcp4o6_ipc.h | 12 | ||||
-rw-r--r-- | src/bin/dhcp4/tests/Makefile.am | 1 | ||||
-rw-r--r-- | src/bin/dhcp4/tests/dhcp4_dhcp4o6_ipc_unittest.cc | 181 | ||||
-rw-r--r-- | src/bin/dhcp6/ctrl_dhcp6_srv.cc | 2 | ||||
-rw-r--r-- | src/bin/dhcp6/dhcp6_dhcp4o6_ipc.cc | 28 | ||||
-rw-r--r-- | src/bin/dhcp6/dhcp6_dhcp4o6_ipc.h | 14 | ||||
-rw-r--r-- | src/bin/dhcp6/dhcp6_srv.cc | 2 | ||||
-rw-r--r-- | src/bin/dhcp6/tests/Makefile.am | 2 | ||||
-rw-r--r-- | src/bin/dhcp6/tests/dhcp6_dhcp4o6_ipc_unittest.cc | 140 | ||||
-rw-r--r-- | src/lib/dhcpsrv/dhcp4o6_ipc.cc | 194 | ||||
-rw-r--r-- | src/lib/dhcpsrv/dhcp4o6_ipc.h | 87 | ||||
-rw-r--r-- | src/lib/dhcpsrv/dhcpsrv_messages.mes | 3 | ||||
-rwxr-xr-x | src/lib/dhcpsrv/tests/Makefile.am | 1 | ||||
-rw-r--r-- | src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc | 71 | ||||
-rw-r--r-- | src/lib/dhcpsrv/testutils/Makefile.am | 3 | ||||
-rw-r--r-- | src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.cc | 47 | ||||
-rw-r--r-- | src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.h | 98 |
19 files changed, 749 insertions, 186 deletions
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc index 5f0f9c1a7d..78eeec3a94 100644 --- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc +++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc @@ -192,7 +192,7 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) { // Setup DHCPv4-over-DHCPv6 IPC try { - Dhcp4o6Ipc::instance().open(); + Dhcp4to6Ipc::instance().open(); } catch (const std::exception& ex) { std::ostringstream err; err << "error starting DHCPv4-over-DHCPv6 IPC " diff --git a/src/bin/dhcp4/dhcp4_dhcp4o6_ipc.cc b/src/bin/dhcp4/dhcp4_dhcp4o6_ipc.cc index 6daf6a6998..b429848c4f 100644 --- a/src/bin/dhcp4/dhcp4_dhcp4o6_ipc.cc +++ b/src/bin/dhcp4/dhcp4_dhcp4o6_ipc.cc @@ -24,14 +24,14 @@ using namespace std; namespace isc { namespace dhcp { -Dhcp4o6Ipc::Dhcp4o6Ipc() : Dhcp4o6IpcBase() {} +Dhcp4to6Ipc::Dhcp4to6Ipc() : Dhcp4o6IpcBase() {} -Dhcp4o6Ipc& Dhcp4o6Ipc::instance() { - static Dhcp4o6Ipc dhcp4o6_ipc; - return (dhcp4o6_ipc); +Dhcp4to6Ipc& Dhcp4to6Ipc::instance() { + static Dhcp4to6Ipc dhcp4to6_ipc; + return (dhcp4to6_ipc); } -void Dhcp4o6Ipc::open() { +void Dhcp4to6Ipc::open() { uint32_t port = CfgMgr::instance().getStagingCfg()->getDhcp4o6Port(); if (port == 0) { Dhcp4o6IpcBase::close(); @@ -42,31 +42,48 @@ void Dhcp4o6Ipc::open() { } int old_fd = socket_fd_; - socket_fd_ = Dhcp4o6IpcBase::open(static_cast<uint16_t>(port), 4); + socket_fd_ = Dhcp4o6IpcBase::open(static_cast<uint16_t>(port), + ENDPOINT_TYPE_V4); if ((old_fd == -1) && (socket_fd_ != old_fd)) { - IfaceMgr::instance().addExternalSocket(socket_fd_, Dhcp4o6Ipc::handler); + IfaceMgr::instance().addExternalSocket(socket_fd_, + Dhcp4to6Ipc::handler); } } -void Dhcp4o6Ipc::handler() { - Dhcp4o6Ipc& ipc = Dhcp4o6Ipc::instance(); +void Dhcp4to6Ipc::handler() { + Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance(); + + // Reset received message in case we return from this method before the + // received message pointer is updated. + ipc.received_.reset(); + + // Receive message from the IPC socket. Pkt6Ptr pkt = ipc.receive(); if (!pkt) { return; } + // Each message must contain option holding DHCPv4 message. OptionCollection msgs = pkt->getOptions(D6O_DHCPV4_MSG); - if (msgs.size() != 1) { - return; + if (msgs.empty()) { + isc_throw(Dhcp4o6IpcError, "DHCPv4 message option not present in the" + " DHCPv4o6 message received by the DHCPv4 server"); + } else if (msgs.size() > 1) { + isc_throw(Dhcp4o6IpcError, "expected exactly one DHCPv4 message within" + " DHCPv4 message option received by the DHCPv4 server"); } - OptionPtr msg = pkt->getOption(D6O_DHCPV4_MSG); + + OptionPtr msg = msgs.begin()->second; if (!msg) { - isc_throw(Unexpected, "Can't get DHCPv4 message option"); + isc_throw(Dhcp4o6IpcError, "null DHCPv4 message option in the" + " DHCPv4o6 message received by the DHCPv4 server"); } - instance().received_.reset(new Pkt4o6(msg->getData(), pkt)); + + // Record this message. + ipc.received_.reset(new Pkt4o6(msg->getData(), pkt)); } -Pkt4o6Ptr& Dhcp4o6Ipc::getReceived() { +Pkt4o6Ptr& Dhcp4to6Ipc::getReceived() { return (received_); } diff --git a/src/bin/dhcp4/dhcp4_dhcp4o6_ipc.h b/src/bin/dhcp4/dhcp4_dhcp4o6_ipc.h index 8a0d98f492..47e5c69dc5 100644 --- a/src/bin/dhcp4/dhcp4_dhcp4o6_ipc.h +++ b/src/bin/dhcp4/dhcp4_dhcp4o6_ipc.h @@ -28,23 +28,23 @@ namespace isc { namespace dhcp { /// @brief Handles DHCPv4-over-DHCPv6 IPC on the DHCPv4 server side -class Dhcp4o6Ipc : public Dhcp4o6IpcBase { +class Dhcp4to6Ipc : public Dhcp4o6IpcBase { protected: /// @brief Constructor /// /// Default constructor - Dhcp4o6Ipc(); + Dhcp4to6Ipc(); /// @brief Destructor. - virtual ~Dhcp4o6Ipc() { } + virtual ~Dhcp4to6Ipc() { } public: - /// @brief Returns pointer to the sole instance of Dhcp4o6Ipc + /// @brief Returns pointer to the sole instance of Dhcp4to6Ipc /// - /// Dhcp4o6Ipc is a singleton class + /// Dhcp4to6Ipc is a singleton class /// /// @return the only existing instance of DHCP4o6 IPC - static Dhcp4o6Ipc& instance(); + static Dhcp4to6Ipc& instance(); /// @brief Open communication socket /// diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am index a0856b21fd..302d9afc61 100644 --- a/src/bin/dhcp4/tests/Makefile.am +++ b/src/bin/dhcp4/tests/Makefile.am @@ -93,6 +93,7 @@ dhcp4_unittests_SOURCES += release_unittest.cc dhcp4_unittests_SOURCES += out_of_range_unittest.cc dhcp4_unittests_SOURCES += decline_unittest.cc dhcp4_unittests_SOURCES += kea_controller_unittest.cc +dhcp4_unittests_SOURCES += dhcp4_dhcp4o6_ipc_unittest.cc nodist_dhcp4_unittests_SOURCES = marker_file.h test_libraries.h diff --git a/src/bin/dhcp4/tests/dhcp4_dhcp4o6_ipc_unittest.cc b/src/bin/dhcp4/tests/dhcp4_dhcp4o6_ipc_unittest.cc new file mode 100644 index 0000000000..7af25833d9 --- /dev/null +++ b/src/bin/dhcp4/tests/dhcp4_dhcp4o6_ipc_unittest.cc @@ -0,0 +1,181 @@ +// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include <config.h> +#include <asiolink/io_address.h> +#include <dhcp/pkt4o6.h> +#include <dhcp/pkt6.h> +#include <dhcp/tests/iface_mgr_test_config.h> +#include <dhcp4/dhcp4_dhcp4o6_ipc.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/testutils/dhcp4o6_test_ipc.h> +#include <gtest/gtest.h> +#include <stdint.h> + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::dhcp::test; +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 ::testing::Test { +public: + + /// @brief Constructor + /// + /// Configures IPC to use a test port. It also provides a fake + /// configuration of interfaces. + Dhcp4to6IpcTest() + : iface_mgr_test_config_(true) { + configurePort(TEST_PORT); + } + + /// @brief Configure DHCP4o6 port. + /// + /// @param port New port. + void configurePort(uint16_t port); + + /// @brief Creates an instance of the DHCPv4o6 Message option. + /// + /// @return Pointer to the instance of the DHCPv4-query Message option. + OptionPtr createDHCPv4MsgOption() const; + +private: + + /// @brief Provides fake configuration of interfaces. + IfaceMgrTestConfig iface_mgr_test_config_; + +}; + +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(); + OutputBuffer& output_buffer = pkt->getBuffer(); + const uint8_t* data = static_cast<const uint8_t*>(output_buffer.getData()); + OptionBuffer option_buffer(data, data + output_buffer.getLength()); + + // 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->setRemoteAddr(IOAddress("2001:db8:1::123")); + ASSERT_NO_THROW(pkt->pack()); + + // 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. + Pkt4o6Ptr pkt_received = ipc.getReceived(); + ASSERT_TRUE(pkt_received); + Pkt6Ptr pkt6_received = pkt_received->getPkt6(); + ASSERT_TRUE(pkt6_received); + EXPECT_EQ("eth0", pkt6_received->getIface()); + EXPECT_EQ("2001:db8:1::123", pkt6_received->getRemoteAddr().toText()); +} + +// 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->setRemoteAddr(IOAddress("2001:db8:1::123")); + ASSERT_NO_THROW(pkt->pack()); + + // Send message. + ASSERT_NO_THROW(src_ipc.send(pkt)); + // Reception handler should throw exception. + EXPECT_THROW(IfaceMgr::instance().receive6(1, 0), Dhcp4o6IpcError); +} + +// 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->setRemoteAddr(IOAddress("2001:db8:1::123")); + ASSERT_NO_THROW(pkt->pack()); + + // Send message. + ASSERT_NO_THROW(src_ipc.send(pkt)); + // Reception handler should throw exception. + EXPECT_THROW(IfaceMgr::instance().receive6(1, 0), Dhcp4o6IpcError); +} + +} // end of anonymous namespace diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc index 9b17b0124a..f546ad7c80 100644 --- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc +++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc @@ -216,7 +216,7 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) { // Setup DHCPv4-over-DHCPv6 IPC try { - Dhcp4o6Ipc::instance().open(); + Dhcp6to4Ipc::instance().open(); } catch (const std::exception& ex) { std::ostringstream err; err << "error starting DHCPv4-over-DHCPv6 IPC " diff --git a/src/bin/dhcp6/dhcp6_dhcp4o6_ipc.cc b/src/bin/dhcp6/dhcp6_dhcp4o6_ipc.cc index 260db36d80..9cd203f404 100644 --- a/src/bin/dhcp6/dhcp6_dhcp4o6_ipc.cc +++ b/src/bin/dhcp6/dhcp6_dhcp4o6_ipc.cc @@ -24,14 +24,14 @@ using namespace std; namespace isc { namespace dhcp { -Dhcp4o6Ipc::Dhcp4o6Ipc() : Dhcp4o6IpcBase() {} +Dhcp6to4Ipc::Dhcp6to4Ipc() : Dhcp4o6IpcBase() {} -Dhcp4o6Ipc& Dhcp4o6Ipc::instance() { - static Dhcp4o6Ipc dhcp4o6_ipc; - return (dhcp4o6_ipc); +Dhcp6to4Ipc& Dhcp6to4Ipc::instance() { + static Dhcp6to4Ipc dhcp6to4_ipc; + return (dhcp6to4_ipc); } -void Dhcp4o6Ipc::open() { +void Dhcp6to4Ipc::open() { uint32_t port = CfgMgr::instance().getStagingCfg()->getDhcp4o6Port(); if (port == 0) { Dhcp4o6IpcBase::close(); @@ -42,30 +42,38 @@ void Dhcp4o6Ipc::open() { } int old_fd = socket_fd_; - socket_fd_ = Dhcp4o6IpcBase::open(static_cast<uint16_t>(port), 6); + socket_fd_ = Dhcp4o6IpcBase::open(static_cast<uint16_t>(port), + ENDPOINT_TYPE_V6); if ((old_fd == -1) && (socket_fd_ != old_fd)) { - IfaceMgr::instance().addExternalSocket(socket_fd_, Dhcp4o6Ipc::handler); + IfaceMgr::instance().addExternalSocket(socket_fd_, + Dhcp6to4Ipc::handler); } } -void Dhcp4o6Ipc::handler() { - Dhcp4o6Ipc& ipc = Dhcp4o6Ipc::instance(); +void Dhcp6to4Ipc::handler() { + Dhcp6to4Ipc& ipc = Dhcp6to4Ipc::instance(); + + // Receive message from IPC. Pkt6Ptr pkt = ipc.receive(); if (!pkt) { return; } + // The received message has been unpacked by the receive() function. This + // method could have modified the message so it's better to pack() it + // again because we'll be forwarding it to a client. isc::util::OutputBuffer& buf = pkt->getBuffer(); buf.clear(); pkt->pack(); - uint8_t msg_type = buf[0]; + uint8_t msg_type = pkt->getType(); if ((msg_type == DHCPV6_RELAY_FORW) || (msg_type == DHCPV6_RELAY_REPL)) { pkt->setRemotePort(DHCP6_SERVER_PORT); } else { pkt->setRemotePort(DHCP6_CLIENT_PORT); } + // Forward packet to the client. IfaceMgr::instance().send(pkt); // processStatsSent(pkt); } diff --git a/src/bin/dhcp6/dhcp6_dhcp4o6_ipc.h b/src/bin/dhcp6/dhcp6_dhcp4o6_ipc.h index 76c5b7e4ce..e8934e7227 100644 --- a/src/bin/dhcp6/dhcp6_dhcp4o6_ipc.h +++ b/src/bin/dhcp6/dhcp6_dhcp4o6_ipc.h @@ -15,7 +15,7 @@ #ifndef DHCP6_DHCP4O6_IPC_H #define DHCP6_DHCP4O6_IPC_H -/// @file dhcp6_dhcp4o6_ipc.h Defines the Dhcp4o6Ipc class. +/// @file dhcp6_dhcp4o6_ipc.h Defines the Dhcp6to4Ipc class. /// This file defines the class Kea uses to act as the DHCPv6 server /// side of DHCPv4-over-DHCPv6 communication between servers. /// @@ -26,23 +26,23 @@ namespace isc { namespace dhcp { /// @brief Handles DHCPv4-over-DHCPv6 IPC on the DHCPv6 server side -class Dhcp4o6Ipc : public Dhcp4o6IpcBase { +class Dhcp6to4Ipc : public Dhcp4o6IpcBase { protected: /// @brief Constructor /// /// Default constructor - Dhcp4o6Ipc(); + Dhcp6to4Ipc(); /// @brief Destructor. - virtual ~Dhcp4o6Ipc() { } + virtual ~Dhcp6to4Ipc() { } public: - /// @brief Returns pointer to the sole instance of Dhcp4o6Ipc + /// @brief Returns pointer to the sole instance of Dhcp6to4Ipc /// - /// Dhcp4o6Ipc is a singleton class + /// Dhcp6to4Ipc is a singleton class /// /// @return the only existing instance of DHCP4o6 IPC - static Dhcp4o6Ipc& instance(); + static Dhcp6to4Ipc& instance(); /// @brief Open communication socket /// diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 3faa0d8738..30f095ab22 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -213,7 +213,7 @@ Dhcpv6Srv::~Dhcpv6Srv() { } try { - Dhcp4o6Ipc::instance().close(); + Dhcp6to4Ipc::instance().close(); } catch(const std::exception& ex) { // Highly unlikely, but lets Report it but go on // LOG_ERROR(dhcp6_logger, DHCP6_SRV_DHCP4O6_ERROR).arg(ex.what()); diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am index 87dc69dd88..4076849cfa 100644 --- a/src/bin/dhcp6/tests/Makefile.am +++ b/src/bin/dhcp6/tests/Makefile.am @@ -94,8 +94,8 @@ dhcp6_unittests_SOURCES += confirm_unittest.cc dhcp6_unittests_SOURCES += infrequest_unittest.cc dhcp6_unittests_SOURCES += decline_unittest.cc dhcp6_unittests_SOURCES += dhcp6_message_test.cc dhcp6_message_test.h - dhcp6_unittests_SOURCES += kea_controller_unittest.cc +dhcp6_unittests_SOURCES += dhcp6_dhcp4o6_ipc_unittest.cc nodist_dhcp6_unittests_SOURCES = marker_file.h test_libraries.h diff --git a/src/bin/dhcp6/tests/dhcp6_dhcp4o6_ipc_unittest.cc b/src/bin/dhcp6/tests/dhcp6_dhcp4o6_ipc_unittest.cc new file mode 100644 index 0000000000..b22dec1683 --- /dev/null +++ b/src/bin/dhcp6/tests/dhcp6_dhcp4o6_ipc_unittest.cc @@ -0,0 +1,140 @@ +// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include <config.h> +#include <asiolink/io_address.h> +#include <dhcp/pkt6.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/tests/iface_mgr_test_config.h> +#include <dhcp/tests/pkt_filter6_test_stub.h> +#include <dhcp6/dhcp6_dhcp4o6_ipc.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/testutils/dhcp4o6_test_ipc.h> +#include <gtest/gtest.h> +#include <stdint.h> + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::dhcp::test; +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 Dhcp6to4IpcTest : public ::testing::Test { +public: + + /// @brief Constructor + /// + /// Configures IPC to use a test port. It also provides a fake + /// configuration of interfaces and opens IPv6 sockets. + Dhcp6to4IpcTest() + : iface_mgr_test_config_(true) { + IfaceMgr::instance().openSockets6(); + configurePort(TEST_PORT); + } + + /// @brief Configure DHCP4o6 port. + /// + /// @param port New port. + void configurePort(const uint16_t port); + + /// @brief Creates an instance of the DHCPv4o6 Message option. + /// + /// @return Pointer to the instance of the DHCPv4-query Message option. + OptionPtr createDHCPv4MsgOption() const; + +private: + + /// @brief Provides fake configuration of interfaces. + IfaceMgrTestConfig iface_mgr_test_config_; +}; + +void +Dhcp6to4IpcTest::configurePort(const uint16_t port) { + CfgMgr::instance().getStagingCfg()->setDhcp4o6Port(port); +} + +OptionPtr +Dhcp6to4IpcTest::createDHCPv4MsgOption() const { + // Create the DHCPv4 message. + Pkt4Ptr pkt(new Pkt4(DHCPREQUEST, 1234)); + // Make a wire representation of the DHCPv4 message. + pkt->pack(); + OutputBuffer& output_buffer = pkt->getBuffer(); + const uint8_t* data = static_cast<const uint8_t*>(output_buffer.getData()); + OptionBuffer option_buffer(data, data + output_buffer.getLength()); + + // 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(Dhcp6to4IpcTest, invalidPortError) { + // Create instance of the IPC endpoint under test with out-of-range port. + configurePort(65535); + Dhcp6to4Ipc& ipc = Dhcp6to4Ipc::instance(); + EXPECT_THROW(ipc.open(), isc::OutOfRange); +} + +// This test verifies that the DHCPv4 endpoint of the DHCPv4o6 IPC can +// receive messages. +TEST_F(Dhcp6to4IpcTest, receive) { + // Create instance of the IPC endpoint under test. + Dhcp6to4Ipc& ipc = Dhcp6to4Ipc::instance(); + // Create instance of the IPC endpoint being used as a source of messages. + TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4); + + // 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->setRemoteAddr(IOAddress("2001:db8:1::123")); + ASSERT_NO_THROW(pkt->pack()); + + // 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)); + +#if 0 + // The stub packet filter exposes static function to retrieve messages + // sent over the fake sockets/interfaces. This is the message that the + // IPC endpoint should forward to the client after receiving it + // from the DHCPv4 server. + Pkt6Ptr forwarded = PktFilter6TestStub::popSent(); + ASSERT_TRUE(forwarded); + + // Verify the packet received. + EXPECT_EQ(DHCP6_CLIENT_PORT, forwarded->getRemotePort()); + EXPECT_EQ(forwarded->getType(), pkt->getType()); + EXPECT_TRUE(forwarded->getOption(D6O_DHCPV4_MSG)); + EXPECT_EQ("eth0", forwarded->getIface()); + EXPECT_EQ("2001:db8:1::123", forwarded->getRemoteAddr().toText()); +#endif +} + +} // end of anonymous namespace diff --git a/src/lib/dhcpsrv/dhcp4o6_ipc.cc b/src/lib/dhcpsrv/dhcp4o6_ipc.cc index bf3567a8e1..a4a5ea2d67 100644 --- a/src/lib/dhcpsrv/dhcp4o6_ipc.cc +++ b/src/lib/dhcpsrv/dhcp4o6_ipc.cc @@ -17,11 +17,12 @@ #include <dhcp/dhcp6.h> #include <dhcp/iface_mgr.h> #include <dhcp/option6_addrlst.h> +#include <dhcp/option_custom.h> #include <dhcp/option_string.h> #include <dhcp/option_vendor.h> #include <dhcpsrv/dhcp4o6_ipc.h> -#include <dhcpsrv/dhcpsrv_log.h> - +#include <boost/pointer_cast.hpp> +#include <errno.h> #include <netinet/in.h> #include <sys/fcntl.h> #include <string> @@ -39,41 +40,36 @@ Dhcp4o6IpcBase::~Dhcp4o6IpcBase() { close(); } -int Dhcp4o6IpcBase::open(uint16_t port, int side) { +int Dhcp4o6IpcBase::open(uint16_t port, EndpointType endpoint_type) { + // Don't check if the value is greater than 65534 as it is done + // by callers before they cast the value to 16 bits. + if (port == port_) { // No change: nothing to do return (socket_fd_); } - // Port 0: closing - if (port == 0) { - port_ = 0; - if (socket_fd_ != -1) { - IfaceMgr::instance().deleteExternalSocket(socket_fd_); - ::close(socket_fd_); - socket_fd_ = -1; - } - return (socket_fd_); - } - // Open socket int sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (sock < 0) { - isc_throw(Unexpected, "Failed to create DHCP4o6 socket."); + isc_throw(Dhcp4o6IpcError, "Failed to create DHCP4o6 socket."); } // Set reuse address int flag = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, - (char *)&flag, sizeof(flag)) < 0) { + static_cast<const void*>(&flag), + sizeof(flag)) < 0) { ::close(sock); - isc_throw(Unexpected, "Failed to set SO_REUSEADDR on DHCP4o6 socket."); + isc_throw(Dhcp4o6IpcError, + "Failed to set SO_REUSEADDR on DHCP4o6 socket."); } // Set no blocking if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) { ::close(sock); - isc_throw(Unexpected, "Failed to set O_NONBLOCK on DHCP4o6 socket."); + isc_throw(Dhcp4o6IpcError, + "Failed to set O_NONBLOCK on DHCP4o6 socket."); } // Bind to the local address @@ -83,7 +79,7 @@ int Dhcp4o6IpcBase::open(uint16_t port, int side) { #ifdef HAVE_SA_LEN local6.sin6_len = sizeof(local6); #endif - if (side == 6) { + if (endpoint_type == ENDPOINT_TYPE_V6) { local6.sin6_port = htons(port); } else { local6.sin6_port = htons(port + 1); @@ -92,7 +88,7 @@ int Dhcp4o6IpcBase::open(uint16_t port, int side) { local6.sin6_addr.s6_addr[15] = 1; if (bind(sock, (struct sockaddr *)&local6, sizeof(local6)) < 0) { ::close(sock); - isc_throw(Unexpected, "Failed to bind DHCP4o6 socket."); + isc_throw(Dhcp4o6IpcError, "Failed to bind DHCP4o6 socket."); } // Connect to the remote address @@ -102,7 +98,7 @@ int Dhcp4o6IpcBase::open(uint16_t port, int side) { #ifdef HAVE_SA_LEN remote6.sin6_len = sizeof(remote6); #endif - if (side == 6) { + if (endpoint_type == ENDPOINT_TYPE_V6) { remote6.sin6_port = htons(port + 1); } else { remote6.sin6_port = htons(port); @@ -113,13 +109,13 @@ int Dhcp4o6IpcBase::open(uint16_t port, int side) { if (connect(sock, reinterpret_cast<const struct sockaddr*>(&remote6), sizeof(remote6)) < 0) { ::close(sock); - isc_throw(Unexpected, "Failed to connect DHCP4o6 socket."); + isc_throw(Dhcp4o6IpcError, "Failed to connect DHCP4o6 socket."); } if (socket_fd_ != -1) { if (dup2(sock, socket_fd_) == -1) { ::close(sock); - isc_throw(Unexpected, "Failed to duplicate DHCP4o6 socket."); + isc_throw(Dhcp4o6IpcError, "Failed to duplicate DHCP4o6 socket."); } if (sock != socket_fd_) { ::close(sock); @@ -134,113 +130,141 @@ int Dhcp4o6IpcBase::open(uint16_t port, int side) { } void Dhcp4o6IpcBase::close() { - static_cast<void>(open(0, 0)); + port_ = 0; + if (socket_fd_ != -1) { + IfaceMgr::instance().deleteExternalSocket(socket_fd_); + ::close(socket_fd_); + socket_fd_ = -1; + } } Pkt6Ptr Dhcp4o6IpcBase::receive() { uint8_t buf[65536]; ssize_t cc = recv(socket_fd_, buf, sizeof(buf), 0); if (cc < 0) { - isc_throw(Unexpected, "Failed to receive on DHCP4o6 socket."); + isc_throw(Dhcp4o6IpcError, "Failed to receive on DHCP4o6 socket."); } Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(buf, cc)); pkt->updateTimestamp(); // Get interface name and remote address pkt->unpack(); - OptionVendorPtr vendor = - boost::dynamic_pointer_cast<OptionVendor>(pkt->getOption(D6O_VENDOR_OPTS)); - if (!vendor) { - LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, - DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET) - .arg("no vendor option"); - return (Pkt6Ptr()); - } - if (vendor->getVendorId() != ENTERPRISE_ID_ISC) { - LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, - DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET) - .arg("vendor option enterprise ID is not ISC"); - return (Pkt6Ptr()); - } - OptionStringPtr ifname = - boost::dynamic_pointer_cast<OptionString>(vendor->getOption(ISC_V6_4O6_INTERFACE)); + + // Vendor option is initially NULL. If we find the instance of the vendor + // option with the ISC enterprise id this pointer will point to it. + OptionVendorPtr option_vendor; + + // Get all vendor option and look for the one with the ISC enterprise id. + OptionCollection vendor_options = pkt->getOptions(D6O_VENDOR_OPTS); + for (OptionCollection::const_iterator opt = vendor_options.begin(); + opt != vendor_options.end(); ++opt) { + option_vendor = boost::dynamic_pointer_cast<OptionVendor>(opt->second); + if (option_vendor) { + if (option_vendor->getVendorId() == ENTERPRISE_ID_ISC) { + break; + } + option_vendor.reset(); + } + } + + // Vendor option must exist. + if (!option_vendor) { + isc_throw(Dhcp4o6IpcError, "option " << D6O_VENDOR_OPTS + << " with ISC enterprise id is not present in the DHCP4o6" + " message sent between the servers"); + } + + // The option carrying interface name is required. + OptionStringPtr ifname = boost::dynamic_pointer_cast< + OptionString>(option_vendor->getOption(ISC_V6_4O6_INTERFACE)); if (!ifname) { - LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, - DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET) - .arg("no interface suboption"); - return (Pkt6Ptr()); + isc_throw(Dhcp4o6IpcError, "option " << D6O_VENDOR_OPTS + << " doesn't contain the " << ISC_V6_4O6_INTERFACE + << " option required in the DHCP4o6 message sent" + " between Kea servers"); } + + // Check if this interface is present in the system. IfacePtr iface = IfaceMgr::instance().getIface(ifname->getValue()); if (!iface) { - LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, - DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET) - .arg("can't get interface " + ifname->getValue()); - return (Pkt6Ptr()); + isc_throw(Dhcp4o6IpcError, "option " << ISC_V6_4O6_INTERFACE + << " sent in the DHCP4o6 message contains non-existing" + " interface name '" << ifname->getValue() << "'"); } - Option6AddrLstPtr srcs = - boost::dynamic_pointer_cast<Option6AddrLst>(vendor->getOption(ISC_V6_4O6_SRC_ADDRESS)); + + // Get the option holding source IPv6 address. + OptionCustomPtr srcs = boost::dynamic_pointer_cast< + OptionCustom>(option_vendor->getOption(ISC_V6_4O6_SRC_ADDRESS)); if (!srcs) { - LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, - DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET) - .arg("no source address suboption"); - return (Pkt6Ptr()); - } - Option6AddrLst::AddressContainer addrs = srcs->getAddresses(); - if (addrs.size() != 1) { - LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, - DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET) - .arg("bad source address suboption"); - return (Pkt6Ptr()); - } - - // Update the packet and return it - static_cast<void>(pkt->delOption(D6O_VENDOR_OPTS)); - pkt->setRemoteAddr(addrs[0]); + isc_throw(Dhcp4o6IpcError, "option " << D6O_VENDOR_OPTS + << " doesn't contain the " << ISC_V6_4O6_SRC_ADDRESS + << " option required in the DHCP4o6 message sent" + " between Kea servers"); + } + + // Update the packet. + pkt->setRemoteAddr(srcs->readAddress()); pkt->setIface(iface->getName()); pkt->setIndex(iface->getIndex()); + + // Remove options that have been added by the IPC sender. + static_cast<void>(option_vendor->delOption(ISC_V6_4O6_INTERFACE)); + static_cast<void>(option_vendor->delOption(ISC_V6_4O6_SRC_ADDRESS)); + + // If there are no more options, the IPC sender has probably created the + // vendor option, in which case we should remove it here. + if (option_vendor->getOptions().empty()) { + static_cast<void>(pkt->delOption(D6O_VENDOR_OPTS)); + } + return (pkt); } -void Dhcp4o6IpcBase::send(Pkt6Ptr pkt) { - // No packet: nothing to send +void Dhcp4o6IpcBase::send(const Pkt6Ptr& pkt) { + // This shouldn't happen, i.e. send() shouldn't be called if there is + // no message. if (!pkt) { - return; + isc_throw(Dhcp4o6IpcError, "DHCP4o6 message must not be NULL while" + " trying to send it over the IPC"); } // Disabled: nowhere to send if (socket_fd_ == -1) { - return; + isc_throw(Dhcp4o6IpcError, "unable to send DHCP4o6 message because" + " IPC socket is closed"); } // Check if vendor option exists. - OptionVendorPtr vendor_opt = boost::dynamic_pointer_cast< + OptionVendorPtr option_vendor = boost::dynamic_pointer_cast< OptionVendor>(pkt->getOption(D6O_VENDOR_OPTS)); // If vendor option doesn't exist or its enterprise id is not ISC's // enterprise id, let's create it. - if (!vendor_opt || (vendor_opt->getVendorId() != ENTERPRISE_ID_ISC)) { - vendor_opt.reset(new OptionVendor(Option::V6, ENTERPRISE_ID_ISC)); + if (!option_vendor || + (option_vendor->getVendorId() != ENTERPRISE_ID_ISC)) { + option_vendor.reset(new OptionVendor(Option::V6, ENTERPRISE_ID_ISC)); + pkt->addOption(option_vendor); } // Push interface name and source address in it - vendor_opt->addOption(OptionPtr(new OptionString(Option::V6, - ISC_V6_4O6_INTERFACE, - pkt->getIface()))); - vendor_opt->addOption(OptionPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS, + option_vendor->addOption(OptionStringPtr(new OptionString(Option::V6, + ISC_V6_4O6_INTERFACE, + pkt->getIface()))); + option_vendor->addOption(Option6AddrLstPtr(new Option6AddrLst( + ISC_V6_4O6_SRC_ADDRESS, pkt->getRemoteAddr()))); - pkt->addOption(vendor_opt); - // Get packet content OutputBuffer& buf = pkt->getBuffer(); buf.clear(); pkt->pack(); - // Send - if (::send(socket_fd_, buf.getData(), buf.getLength(), 0) < 0) { - isc_throw(Unexpected, "Failed to send over DHCP4o6 IPC socket"); - } - return; + // Try to send the message. + if (::send(socket_fd_, buf.getData(), buf.getLength(), 0) < 0) { + isc_throw(Dhcp4o6IpcError, + "failed to send DHCP4o6 message over the IPC: " + << strerror(errno)); + } } }; // namespace dhcp diff --git a/src/lib/dhcpsrv/dhcp4o6_ipc.h b/src/lib/dhcpsrv/dhcp4o6_ipc.h index 8097d2117e..21fa2ec526 100644 --- a/src/lib/dhcpsrv/dhcp4o6_ipc.h +++ b/src/lib/dhcpsrv/dhcp4o6_ipc.h @@ -19,18 +19,61 @@ /// This file defines the class Kea uses as a base for /// DHCPv4-over-DHCPv6 communication between servers. /// -#include <dhcp/pkt6.h> +#include <exceptions/exceptions.h> +#include <dhcp/pkt6.h> #include <boost/noncopyable.hpp> - #include <stdint.h> namespace isc { namespace dhcp { -/// @brief +/// @brief Exception thrown when error occurs as a result of use of IPC. +class Dhcp4o6IpcError : public Exception { +public: + Dhcp4o6IpcError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Base class implementing transmission of the DHCPv4 over +/// DHCPv6 messages (RFC 7341) between the Kea servers. +/// +/// When the DHCPv6 server receives the DHCPv4 query message it needs +/// to forward it to the DHCPv4 server for processing. The DHCPv4 +/// server processes the message and answers with the DHCPv4 response +/// message to the DHCPv6 server. The server forwards it back to the +/// client. This class implements the communication between the DHCPv4 +/// and DHCPv6 servers to allow for transmission of the DHCPv4 query +/// and DHCPv6 response messages. +/// +/// This class creates a socket (when @c open is called) and binds it +/// to a port, depending on the configuration. The port number is +/// explicitly specified in the server configuration. This explicit +/// port value is used directly on the DHCPv6 server side. The DHCPv4 +/// server uses the port specified + 1. /// +/// The DHCPv4 and DHCPv6 servers use distict instances of classes derived +/// from this base class. Each of these instances is used to send and +/// receive messages sent by the other server. +/// +/// In order to make address allocation decisions, the DHCPv4 server +/// requires information about the interface and the source address of +/// the original DHCPv4 query message sent by the client. This +/// information is known by the DHCPv6 server and needs to be conveyed +/// to the DHCPv4 server. The IPC conveys it in the +/// @c ISC_V6_4O6_INTERFACE and @c ISC_V6_4O6_SRC_ADDRESS options +/// within the Vendor Specific Information option, with ISC +/// enterprise id. These options are added by the IPC sender and removed +/// by the IPC receiver. class Dhcp4o6IpcBase : public boost::noncopyable { +public: + + /// @brief Endpoint type: DHCPv4 or DHCPv6 server. + enum EndpointType { + ENDPOINT_TYPE_V4 = 4, + ENDPOINT_TYPE_V6 = 6 + }; + protected: /// @brief Constructor /// @@ -40,38 +83,48 @@ protected: /// @brief Destructor. virtual ~Dhcp4o6IpcBase(); - /// @brief Open communication socket (from base class) + /// @brief Open communication socket (from base class). /// - /// @param port port number to use (0 for disabled) - /// @param side side of the server (4 or 6) + /// @param port Port number to use. The socket is bound to this port + /// if the endpoint type is DHCPv6 server, otherwise the port + 1 + /// value is used. + /// @param endpoint_type Endpoint type (DHCPv4 or DHCPv6 server). /// - /// @return new socket descriptor - int open(uint16_t port, int side); + /// @return New socket descriptor. + int open(uint16_t port, EndpointType endpoint_type); public: - /// @brief Open communication socket (for derived classes) + + /// @brief Open communication socket (for derived classes). virtual void open() = 0; - /// @brief Close communication socket + /// @brief Close communication socket. void close(); - /// @brief Receive IPC message + /// @brief Receive message over IPC. /// /// @return a pointer to a DHCPv6 message with interface and remote /// address set from the IPC message Pkt6Ptr receive(); - /// @brief Send IPC message + /// @brief Send message over IPC. + /// + /// The IPC uses @c ISC_V6_4O6_INTERFACE and @c ISC_V6_4O6_SRC_ADDRESS + /// options conveyed within the Vendor Specific Information option, with + /// ISC enterprise id, to communicate the client remote address and the + /// interface on which the DHCPv4 query was received. These options will + /// be removed by the receiver. /// - /// @param a pointer to a DHCPv6 message with interface and remote - /// address set for the IPC message - void send(Pkt6Ptr pkt); + /// @param pkt Pointer to a DHCPv6 message with interface and remote + /// address. + void send(const Pkt6Ptr& pkt); protected: - /// @brief Port number + + /// @brief Port number configured for IPC communication. uint16_t port_; - /// @brief Socket descriptor + /// @brief Socket descriptor. int socket_fd_; }; diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes index 201761a4a7..4821a4342b 100644 --- a/src/lib/dhcpsrv/dhcpsrv_messages.mes +++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes @@ -151,9 +151,6 @@ the database access parameters are changed: in the latter case, the server closes the currently open database, and opens a database using the new parameters. -% DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET received bad DHCPv4o6 packet: %1 -A bad DHCPv4o6 packet was received. - % DHCPSRV_DHCP_DDNS_ERROR_EXCEPTION error handler for DHCP_DDNS IO generated an expected exception: %1 This is an error message that occurs when an attempt to send a request to kea-dhcp-ddns fails there registered error handler threw an uncaught exception. diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am index b8c766f497..64f793c456 100755 --- a/src/lib/dhcpsrv/tests/Makefile.am +++ b/src/lib/dhcpsrv/tests/Makefile.am @@ -92,6 +92,7 @@ libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc libdhcpsrv_unittests_SOURCES += daemon_unittest.cc libdhcpsrv_unittests_SOURCES += database_connection_unittest.cc libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc +libdhcpsrv_unittests_SOURCES += dhcp4o6_ipc_unittest.cc libdhcpsrv_unittests_SOURCES += duid_config_parser_unittest.cc libdhcpsrv_unittests_SOURCES += expiration_config_parser_unittest.cc libdhcpsrv_unittests_SOURCES += host_mgr_unittest.cc diff --git a/src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc b/src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc index c9e8db199a..1ab1c6461b 100644 --- a/src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc +++ b/src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc @@ -42,7 +42,7 @@ const uint16_t TEST_PORT = 12345; const uint16_t TEST_ITERATIONS = 10; /// @brief Type definition for the function creating DHCP message. -typedef boost::function<Pkt6Ptr(const uint16_t, const uint16_t)> CreateMsgFun; +typedef boost::function<Pkt6Ptr(uint16_t, uint16_t)> CreateMsgFun; /// @brief Define short name for test IPC class. typedef Dhcp4o6TestIpc TestIpc; @@ -65,7 +65,7 @@ protected: /// @param prefix Prefix. /// @param postfix Postfix. /// @return String representing concatenated prefix and postfix. - static std::string concatenate(const std::string& prefix, const uint16_t postfix); + static std::string concatenate(const std::string& prefix, uint16_t postfix); /// @brief Creates an instance of the DHCPv4o6 message. //// @@ -77,19 +77,20 @@ protected: /// the "eth0" will be used, for odd postfix values "eth1" will be used. /// /// @return Pointer to the created message. - static Pkt6Ptr createDHCPv4o6Message(const uint16_t msg_type, - const uint16_t postfix = 0); + static Pkt6Ptr createDHCPv4o6Message(uint16_t msg_type, + uint16_t postfix = 0); /// @brief Creates an instance of the DHCPv4o6 message with vendor option. /// /// @param msg_type Message type. /// @param postfix Postfix to be appended to the remote address. See the /// documentation of @c createDHCPv4o6Message for details. + /// @param enterprise_id Enterprise ID for the vendor option. /// /// @return Pointer to the created message. - static Pkt6Ptr createDHCPv4o6MsgWithVendorOption(const uint16_t msg_type, - const uint16_t postfix, - const uint32_t enterprise_id); + static Pkt6Ptr createDHCPv4o6MsgWithVendorOption(uint16_t msg_type, + uint16_t postfix, + uint32_t enterprise_id); /// @brief Creates an instance of the DHCPv4o6 message with ISC /// vendor option. @@ -102,8 +103,8 @@ protected: /// documentation of @c createDHCPv4o6Message for details. /// /// @return Pointer to the created message. - static Pkt6Ptr createDHCPv4o6MsgWithISCVendorOption(const uint16_t msg_type, - const uint16_t postfix); + static Pkt6Ptr createDHCPv4o6MsgWithISCVendorOption(uint16_t msg_type, + uint16_t postfix); /// @brief Creates an instance of the DHCPv4o6 message with vendor /// option holding enterprise id of 32000. @@ -117,23 +118,24 @@ protected: /// documentation of @c createDHCPv4o6Message for details. /// /// @return Pointer to the created message. - static Pkt6Ptr createDHCPv4o6MsgWithAnyVendorOption(const uint16_t msg_type, - const uint16_t postfix); + static Pkt6Ptr createDHCPv4o6MsgWithAnyVendorOption(uint16_t msg_type, + uint16_t postfix); /// @brief Creates an instance of the DHCPv4o6 Message option. /// /// @param src Type of the source endpoint. It can be 4 or 6. /// @return Pointer to the instance of the option. - static OptionPtr createDHCPv4MsgOption(const TestIpc::EndpointType& src); + static OptionPtr createDHCPv4MsgOption(TestIpc::EndpointType src); /// @brief Tests sending and receiving packets over the IPC. /// /// @param iterations_num Number of packets to be sent over the IPC. /// @param src Type of the source IPC endpoint. It can be 4 or 6. /// @param dest Type of the destination IPC endpoint. It can be 4 or 6. - void testSendReceive(const uint16_t iterations_num, - const TestIpc::EndpointType& src, - const TestIpc::EndpointType& dest, + /// @param create_msg_fun Function called to create the packet. + void testSendReceive(uint16_t iterations_num, + TestIpc::EndpointType src, + TestIpc::EndpointType dest, const CreateMsgFun& create_msg_fun); /// @brief Tests that error is reported when invalid message is received. @@ -154,15 +156,15 @@ Dhcp4o6IpcBaseTest::Dhcp4o6IpcBaseTest() std::string Dhcp4o6IpcBaseTest::concatenate(const std::string& prefix, - const uint16_t postfix) { + uint16_t postfix) { std::ostringstream s; s << prefix << postfix; return (s.str()); } Pkt6Ptr -Dhcp4o6IpcBaseTest::createDHCPv4o6Message(const uint16_t msg_type, - const uint16_t postfix) { +Dhcp4o6IpcBaseTest::createDHCPv4o6Message(uint16_t msg_type, + uint16_t postfix) { // Create the DHCPv4o6 message. Pkt6Ptr pkt(new Pkt6(msg_type, 0)); @@ -180,7 +182,7 @@ Dhcp4o6IpcBaseTest::createDHCPv4o6Message(const uint16_t msg_type, pkt->setRemoteAddr(IOAddress(concatenate("2001:db8:1::", postfix))); // Determine the endpoint type using the message type. - const TestIpc::EndpointType src = (msg_type == DHCPV6_DHCPV4_QUERY) ? + TestIpc::EndpointType src = (msg_type == DHCPV6_DHCPV4_QUERY) ? TestIpc::ENDPOINT_TYPE_V6 : TestIpc::ENDPOINT_TYPE_V4; // Add DHCPv4 Message option to make sure it is conveyed by the IPC. @@ -190,9 +192,9 @@ Dhcp4o6IpcBaseTest::createDHCPv4o6Message(const uint16_t msg_type, } Pkt6Ptr -Dhcp4o6IpcBaseTest::createDHCPv4o6MsgWithVendorOption(const uint16_t msg_type, - const uint16_t postfix, - const uint32_t enterprise_id) { +Dhcp4o6IpcBaseTest::createDHCPv4o6MsgWithVendorOption(uint16_t msg_type, + uint16_t postfix, + uint32_t enterprise_id) { Pkt6Ptr pkt = createDHCPv4o6Message(msg_type, postfix); // Create vendor option with ISC enterprise id. @@ -208,19 +210,19 @@ Dhcp4o6IpcBaseTest::createDHCPv4o6MsgWithVendorOption(const uint16_t msg_type, } Pkt6Ptr -Dhcp4o6IpcBaseTest::createDHCPv4o6MsgWithISCVendorOption(const uint16_t msg_type, - const uint16_t postfix) { +Dhcp4o6IpcBaseTest::createDHCPv4o6MsgWithISCVendorOption(uint16_t msg_type, + uint16_t postfix) { return (createDHCPv4o6MsgWithVendorOption(msg_type, postfix, ENTERPRISE_ID_ISC)); } Pkt6Ptr -Dhcp4o6IpcBaseTest::createDHCPv4o6MsgWithAnyVendorOption(const uint16_t msg_type, - const uint16_t postfix) { +Dhcp4o6IpcBaseTest::createDHCPv4o6MsgWithAnyVendorOption(uint16_t msg_type, + uint16_t postfix) { return (createDHCPv4o6MsgWithVendorOption(msg_type, postfix, 32000)); } OptionPtr -Dhcp4o6IpcBaseTest::createDHCPv4MsgOption(const TestIpc::EndpointType& src) { +Dhcp4o6IpcBaseTest::createDHCPv4MsgOption(TestIpc::EndpointType src) { // Create the DHCPv4 message. Pkt4Ptr pkt(new Pkt4(src == TestIpc::ENDPOINT_TYPE_V4 ? DHCPACK : DHCPREQUEST, 1234)); @@ -236,9 +238,9 @@ Dhcp4o6IpcBaseTest::createDHCPv4MsgOption(const TestIpc::EndpointType& src) { } void -Dhcp4o6IpcBaseTest::testSendReceive(const uint16_t iterations_num, - const TestIpc::EndpointType& src, - const TestIpc::EndpointType& dest, +Dhcp4o6IpcBaseTest::testSendReceive(uint16_t iterations_num, + TestIpc::EndpointType src, + TestIpc::EndpointType dest, const CreateMsgFun& create_msg_fun) { // Create IPC instances representing the source and destination endpoints. TestIpc ipc_src(TEST_PORT, src); @@ -248,7 +250,7 @@ Dhcp4o6IpcBaseTest::testSendReceive(const uint16_t iterations_num, ASSERT_NO_THROW(ipc_src.open()); ASSERT_NO_THROW(ipc_dest.open()); - // Depnding if we're sending from DHCPv6 to DHCPv4 or the opposite + // Depending if we're sending from DHCPv6 to DHCPv4 or the opposite // direction we use different message type. This is not really required // for testing IPC, but it better simulates the real use case. uint16_t msg_type = (src == TestIpc::ENDPOINT_TYPE_V6 ? DHCPV6_DHCPV4_QUERY : @@ -459,13 +461,6 @@ TEST_F(Dhcp4o6IpcBaseTest, openError) { // This test verifies that the IPC returns an error when trying to bind // to the out of range port. -TEST_F(Dhcp4o6IpcBaseTest, invalidPortError4) { - TestIpc ipc(65535, TestIpc::ENDPOINT_TYPE_V4); - EXPECT_THROW(ipc.open(), Dhcp4o6IpcError); -} - -// This test verifies that the IPC returns an error when trying to bind -// to the out of range port. TEST_F(Dhcp4o6IpcBaseTest, invalidPortError6) { TestIpc ipc(65535, TestIpc::ENDPOINT_TYPE_V6); EXPECT_THROW(ipc.open(), Dhcp4o6IpcError); diff --git a/src/lib/dhcpsrv/testutils/Makefile.am b/src/lib/dhcpsrv/testutils/Makefile.am index f058c553a7..7ff34a859e 100644 --- a/src/lib/dhcpsrv/testutils/Makefile.am +++ b/src/lib/dhcpsrv/testutils/Makefile.am @@ -12,7 +12,8 @@ if HAVE_GTEST noinst_LTLIBRARIES = libdhcpsrvtest.la -libdhcpsrvtest_la_SOURCES = config_result_check.cc config_result_check.h +libdhcpsrvtest_la_SOURCES = config_result_check.cc config_result_check.h +libdhcpsrvtest_la_SOURCES += dhcp4o6_test_ipc.cc dhcp4o6_test_ipc.h if HAVE_MYSQL libdhcpsrvtest_la_SOURCES += schema.cc schema.h diff --git a/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.cc b/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.cc new file mode 100644 index 0000000000..14d25432d9 --- /dev/null +++ b/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.cc @@ -0,0 +1,47 @@ +// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#include <config.h> +#include <dhcp/iface_mgr.h> +#include <dhcpsrv/testutils/dhcp4o6_test_ipc.h> +#include <boost/bind.hpp> + +namespace isc { +namespace dhcp { +namespace test { + +Dhcp4o6TestIpc::Dhcp4o6TestIpc(uint16_t port, EndpointType endpoint_type) + : desired_port_(port), endpoint_type_(endpoint_type), pkt_received_() { +} + +void +Dhcp4o6TestIpc::open() { + // Use the base IPC to open the socket. + socket_fd_ = Dhcp4o6IpcBase::open(desired_port_, endpoint_type_); + // If the socket has been opened correctly, register it in the @c IfaceMgr. + if (socket_fd_ != -1) { + IfaceMgr& iface_mgr = IfaceMgr::instance(); + iface_mgr.addExternalSocket(socket_fd_, + boost::bind(&Dhcp4o6TestIpc::receiveHandler, this)); + } +} + +void +Dhcp4o6TestIpc::receiveHandler() { + pkt_received_ = receive(); +} + +} // end of isc::dhcp::test namespace +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.h b/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.h new file mode 100644 index 0000000000..6cda8068a9 --- /dev/null +++ b/src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.h @@ -0,0 +1,98 @@ +// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#ifndef DHCP4O6_TEST_IPC_H +#define DHCP4O6_TEST_IPC_H + +#include <asiolink/io_address.h> +#include <dhcp/iface_mgr.h> +#include <dhcp/pkt6.h> +#include <dhcpsrv/dhcp4o6_ipc.h> +#include <boost/noncopyable.hpp> +#include <stdint.h> + +namespace isc { +namespace dhcp { +namespace test { + +/// @brief Implements a simple IPC for the test. +class Dhcp4o6TestIpc : public Dhcp4o6IpcBase { +public: + + /// @brief Constructor. + /// + /// @param port Desired port. + /// @param endpoint_type Type of the IPC endpoint. It should be 4 or 6. + Dhcp4o6TestIpc(uint16_t port, EndpointType endpoint_type); + + /// @brief Sets new port to be used with @c open. + /// + /// @param desired_port New desired port. + void setDesiredPort(uint16_t desired_port) { + desired_port_ = desired_port; + } + + /// @brief Opens the IPC socket and registers it in @c IfaceMgr. + /// + /// This method opens the IPC socket and registers it as external + /// socket in the IfaceMgr. The @c TestIpc::receiveHandler is used as a + /// callback to be called by the @c IfaceMgr when the data is received + /// over the socket. + virtual void open(); + + /// @brief Retrieve port which socket is bound to. + uint16_t getPort() const { + return (port_); + } + + /// @brief Retrieve socket descriptor. + int getSocketFd() const { + return (socket_fd_); + } + + /// @brief Pops and returns a received message. + /// + /// @return Pointer to the received message over the IPC. + Pkt6Ptr popPktReceived() { + // Copy the received message. + Pkt6Ptr pkt_copy(pkt_received_); + // Set the received message to NULL (pop). + pkt_received_.reset(); + // Return the copy. + return (pkt_copy); + } + +private: + + /// @brief Callback for the IPC socket. + /// + /// This callback is called by the @c IfaceMgr when the data is received + /// over the IPC socket. + void receiveHandler(); + + /// @brief Port number. + uint16_t desired_port_; + + /// @brief Endpoint type, i.e. 4 or 6. + EndpointType endpoint_type_; + + /// @brief Pointer to the last received message. + Pkt6Ptr pkt_received_; +}; + +}; // end of isc::dhcp::test namespace +}; // end of isc::dhcp namespace +}; // end of isc namespace + +#endif // DHCP4O6_TEST_IPC_H |