Browse Source

[master] Merge branch 'trac4106'

Marcin Siodelski 9 years ago
parent
commit
8f2a1f8a7a

+ 1 - 0
src/bin/dhcp4/Makefile.am

@@ -62,6 +62,7 @@ libdhcp4_la_SOURCES += ctrl_dhcp4_srv.cc ctrl_dhcp4_srv.h
 libdhcp4_la_SOURCES += json_config_parser.cc json_config_parser.h
 libdhcp4_la_SOURCES += dhcp4_log.cc dhcp4_log.h
 libdhcp4_la_SOURCES += dhcp4_srv.cc dhcp4_srv.h
+libdhcp4_la_SOURCES += dhcp4_dhcp4o6_ipc.cc dhcp4_dhcp4o6_ipc.h
 
 libdhcp4_la_SOURCES += kea_controller.cc
 

+ 11 - 0
src/bin/dhcp4/ctrl_dhcp4_srv.cc

@@ -16,6 +16,7 @@
 #include <cc/data.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
+#include <dhcp4/dhcp4_dhcp4o6_ipc.h>
 #include <hooks/hooks_manager.h>
 #include <dhcp4/json_config_parser.h>
 #include <dhcpsrv/cfgmgr.h>
@@ -185,6 +186,16 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
         return (isc::config::createAnswer(1, err.str()));
     }
 
+    // Setup DHCPv4-over-DHCPv6 IPC
+    try {
+        Dhcp4to6Ipc::instance().open();
+    } catch (const std::exception& ex) {
+        std::ostringstream err;
+        err << "error starting DHCPv4-over-DHCPv6 IPC "
+               " after server reconfiguration: " << ex.what();
+        return (isc::config::createAnswer(1, err.str()));
+    }
+
     // Configuration may change active interfaces. Therefore, we have to reopen
     // sockets according to new configuration. It is possible that this
     // operation will fail for some interfaces but the openSockets function

+ 89 - 0
src/bin/dhcp4/dhcp4_dhcp4o6_ipc.cc

@@ -0,0 +1,89 @@
+// Copyright (C) 2015 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 <util/buffer.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcp4/dhcp4_dhcp4o6_ipc.h>
+
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+Dhcp4to6Ipc::Dhcp4to6Ipc() : Dhcp4o6IpcBase() {
+}
+
+Dhcp4to6Ipc& Dhcp4to6Ipc::instance() {
+    static Dhcp4to6Ipc dhcp4to6_ipc;
+    return (dhcp4to6_ipc);
+}
+
+void Dhcp4to6Ipc::open() {
+    uint32_t port = CfgMgr::instance().getStagingCfg()->getDhcp4o6Port();
+    if (port == 0) {
+        Dhcp4o6IpcBase::close();
+        return;
+    }
+
+    int old_fd = socket_fd_;
+    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_, Dhcp4to6Ipc::handler);
+    }
+}
+
+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.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 = msgs.begin()->second;
+    if (!msg) {
+        isc_throw(Dhcp4o6IpcError, "null DHCPv4 message option in the"
+                  " DHCPv4o6 message received by the DHCPv4 server");
+    }
+
+    // Record this message.
+    ipc.received_.reset(new Pkt4o6(msg->getData(), pkt));
+}
+
+Pkt4o6Ptr& Dhcp4to6Ipc::getReceived() {
+    return (received_);
+}
+
+};  // namespace dhcp
+
+};  // namespace isc

+ 74 - 0
src/bin/dhcp4/dhcp4_dhcp4o6_ipc.h

@@ -0,0 +1,74 @@
+// Copyright (C) 2015 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 DHCP4_DHCP4O6_IPC_H
+#define DHCP4_DHCP4O6_IPC_H
+
+/// @file dhcp4_dhcp4o6_ipc.h Defines the Dhcp4o6Ipc class.
+/// This file defines the class Kea uses to act as the DHCPv4 server
+/// side of DHCPv4-over-DHCPv6 communication between servers.
+///
+
+#include <dhcp/pkt4o6.h>
+#include <dhcpsrv/dhcp4o6_ipc.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Handles DHCPv4-over-DHCPv6 IPC on the DHCPv4 server side
+class Dhcp4to6Ipc : public Dhcp4o6IpcBase {
+protected:
+    /// @brief Constructor
+    ///
+    /// Default constructor
+    Dhcp4to6Ipc();
+
+    /// @brief Destructor.
+    virtual ~Dhcp4to6Ipc() { }
+
+public:
+    /// @brief Returns pointer to the sole instance of Dhcp4o6Ipc
+    ///
+    /// Dhcp4to6Ipc is a singleton class
+    ///
+    /// @return the only existing instance of DHCP4o6 IPC
+    static Dhcp4to6Ipc& instance();
+
+    /// @brief Open communication socket
+    ///
+    /// Call base open method and sets the handler/callback when needed
+    virtual void open();
+
+    /// @brief On receive handler
+    ///
+    /// The handler processes the DHCPv4-query DHCPv6 packet and
+    /// sends the DHCPv4-response DHCPv6 packet back to the DHCPv6 server
+    static void handler();
+
+    /// @brief Returns last received packet
+    ///
+    /// @return a reference to a shared pointer to the last received packet
+    /// @note This reference should be cleared after use
+    Pkt4o6Ptr& getReceived();
+
+private:
+    /// @brief last received packet
+    Pkt4o6Ptr received_;
+};
+
+} // namespace isc
+} // namespace dhcp
+
+#endif

+ 13 - 2
src/bin/dhcp4/json_config_parser.cc

@@ -422,9 +422,10 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id,
     if ((config_id.compare("valid-lifetime") == 0)  ||
         (config_id.compare("renew-timer") == 0)  ||
         (config_id.compare("rebind-timer") == 0) ||
-        (config_id.compare("decline-probation-period") == 0) )  {
+        (config_id.compare("decline-probation-period") == 0) ||
+        (config_id.compare("dhcp4o6-port") == 0) )  {
         parser = new Uint32Parser(config_id,
-                                 globalContext()->uint32_values_);
+                                  globalContext()->uint32_values_);
     } else if (config_id.compare("interfaces-config") == 0) {
         parser = new IfacesConfigParser4();
     } else if (config_id.compare("subnet4") == 0) {
@@ -472,6 +473,7 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id,
 ///
 /// - echo-client-id
 /// - decline-probation-period
+/// - dhcp4o6-port
 void setGlobalParameters4() {
     // Although the function is modest for now, it is certain that the number
     // of global switches will increase over time, hence the name.
@@ -494,6 +496,15 @@ void setGlobalParameters4() {
     } catch (...) {
         // That's not really needed.
     }
+
+    // Set the DHCPv4-over-DHCPv6 interserver port.
+    try {
+        uint32_t dhcp4o6_port = globalContext()->uint32_values_
+            ->getOptionalParam("dhcp4o6-port", 0);
+        CfgMgr::instance().getStagingCfg()->setDhcp4o6Port(dhcp4o6_port);
+    } catch (...) {
+        // Ignore errors. This flag is optional
+    }
 }
 
 isc::data::ConstElementPtr

+ 1 - 0
src/bin/dhcp4/tests/Makefile.am

@@ -92,6 +92,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
 

+ 173 - 0
src/bin/dhcp4/tests/dhcp4_dhcp4o6_ipc_unittest.cc

@@ -0,0 +1,173 @@
+// Copyright (C) 2015 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(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
+Dhcp4to6IpcTest::configurePort(const 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 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

+ 1 - 0
src/bin/dhcp6/Makefile.am

@@ -63,6 +63,7 @@ libdhcp6_la_SOURCES += dhcp6_log.cc dhcp6_log.h
 libdhcp6_la_SOURCES += dhcp6_srv.cc dhcp6_srv.h
 libdhcp6_la_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
 libdhcp6_la_SOURCES += json_config_parser.cc json_config_parser.h
+libdhcp6_la_SOURCES += dhcp6_dhcp4o6_ipc.cc dhcp6_dhcp4o6_ipc.h
 
 libdhcp6_la_SOURCES += kea_controller.cc
 

+ 11 - 0
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -18,6 +18,7 @@
 #include <dhcp/libdhcp++.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp6/dhcp6_dhcp4o6_ipc.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/json_config_parser.h>
 #include <hooks/hooks_manager.h>
@@ -209,6 +210,16 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
         return (isc::config::createAnswer(1, err.str()));
     }
 
+    // Setup DHCPv4-over-DHCPv6 IPC
+    try {
+        Dhcp6to4Ipc::instance().open();
+    } catch (const std::exception& ex) {
+        std::ostringstream err;
+        err << "error starting DHCPv4-over-DHCPv6 IPC "
+               " after server reconfiguration: " << ex.what();
+        return (isc::config::createAnswer(1, err.str()));
+    }
+
     // Configuration may change active interfaces. Therefore, we have to reopen
     // sockets according to new configuration. It is possible that this
     // operation will fail for some interfaces but the openSockets function

+ 79 - 0
src/bin/dhcp6/dhcp6_dhcp4o6_ipc.cc

@@ -0,0 +1,79 @@
+// Copyright (C) 2015 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 <util/buffer.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcp6/dhcp6_dhcp4o6_ipc.h>
+
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+Dhcp6to4Ipc::Dhcp6to4Ipc() : Dhcp4o6IpcBase() {}
+
+Dhcp6to4Ipc& Dhcp6to4Ipc::instance() {
+    static Dhcp6to4Ipc dhcp6to4_ipc;
+    return (dhcp6to4_ipc);
+}
+
+void Dhcp6to4Ipc::open() {
+    uint32_t port = CfgMgr::instance().getStagingCfg()->getDhcp4o6Port();
+    if (port == 0) {
+        Dhcp4o6IpcBase::close();
+        return;
+    }
+
+    int old_fd = socket_fd_;
+    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_, Dhcp6to4Ipc::handler);
+    }
+}
+
+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 = 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);
+}
+
+};  // namespace dhcp
+
+};  // namespace isc

+ 61 - 0
src/bin/dhcp6/dhcp6_dhcp4o6_ipc.h

@@ -0,0 +1,61 @@
+// Copyright (C) 2015 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 DHCP6_DHCP4O6_IPC_H
+#define DHCP6_DHCP4O6_IPC_H
+
+/// @file dhcp6_dhcp4o6_ipc.h Defines the Dhcp4o6Ipc class.
+/// This file defines the class Kea uses to act as the DHCPv6 server
+/// side of DHCPv4-over-DHCPv6 communication between servers.
+///
+#include <dhcpsrv/dhcp4o6_ipc.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Handles DHCPv4-over-DHCPv6 IPC on the DHCPv6 server side
+class Dhcp6to4Ipc : public Dhcp4o6IpcBase {
+protected:
+    /// @brief Constructor
+    ///
+    /// Default constructor
+    Dhcp6to4Ipc();
+
+    /// @brief Destructor.
+    virtual ~Dhcp6to4Ipc() { }
+
+public:
+    /// @brief Returns pointer to the sole instance of Dhcp4o6Ipc
+    ///
+    /// Dhcp4o6Ipc is a singleton class
+    ///
+    /// @return the only existing instance of DHCP4o6 IPC
+    static Dhcp6to4Ipc& instance();
+
+    /// @brief Open communication socket
+    ///
+    /// Call base open method and sets the handler/callback when needed
+    virtual void open();
+
+    /// @brief On receive handler
+    ///
+    /// The handler sends the DHCPv6 packet back to the remote address
+    static void handler();
+};
+
+} // namespace isc
+} // namespace dhcp
+
+#endif

+ 8 - 0
src/bin/dhcp6/dhcp6_srv.cc

@@ -33,6 +33,7 @@
 #include <dhcp/option_vendor_class.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/pkt6.h>
+#include <dhcp6/dhcp6_dhcp4o6_ipc.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/dhcp6_srv.h>
 #include <dhcpsrv/callout_handle_store.h>
@@ -220,6 +221,13 @@ Dhcpv6Srv::~Dhcpv6Srv() {
         LOG_ERROR(dhcp6_logger, DHCP6_SRV_D2STOP_ERROR).arg(ex.what());
     }
 
+    try {
+        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());
+    }
+
     IfaceMgr::instance().closeSockets();
 
     LeaseMgrFactory::destroy();

+ 12 - 1
src/bin/dhcp6/json_config_parser.cc

@@ -674,7 +674,8 @@ DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id,
         (config_id.compare("valid-lifetime") == 0)  ||
         (config_id.compare("renew-timer") == 0)  ||
         (config_id.compare("rebind-timer") == 0) ||
-        (config_id.compare("decline-probation-period") == 0) )  {
+        (config_id.compare("decline-probation-period") == 0) ||
+        (config_id.compare("dhcp4o6-port") == 0) )  {
         parser = new Uint32Parser(config_id,
                                  globalContext()->uint32_values_);
     } else if (config_id.compare("interfaces-config") == 0) {
@@ -725,6 +726,7 @@ DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id,
 /// Currently this method sets the following global parameters:
 ///
 /// - decline-probation-period
+/// - dhcp4o6-port
 void setGlobalParameters6() {
 
     // Set the probation period for decline handling.
@@ -736,6 +738,15 @@ void setGlobalParameters6() {
     } catch (...) {
         // That's not really needed.
     }
+
+    // Set the DHCPv4-over-DHCPv6 interserver port.
+    try {
+        uint32_t dhcp4o6_port = globalContext()->uint32_values_
+            ->getOptionalParam("dhcp4o6-port", 0);
+        CfgMgr::instance().getStagingCfg()->setDhcp4o6Port(dhcp4o6_port);
+    } catch (...) {
+        // Ignore errors. This flag is optional
+    }
 }
 
 isc::data::ConstElementPtr

+ 1 - 1
src/bin/dhcp6/tests/Makefile.am

@@ -93,8 +93,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
 

+ 130 - 0
src/bin/dhcp6/tests/dhcp6_dhcp4o6_ipc_unittest.cc

@@ -0,0 +1,130 @@
+// Copyright (C) 2015 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 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));
+
+
+    // 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());
+}
+
+} // end of anonymous namespace

+ 64 - 3
src/lib/dhcp/tests/pkt_filter6_test_stub.cc

@@ -13,21 +13,70 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <config.h>
-
+#include <exceptions/exceptions.h>
 #include <dhcp/tests/pkt_filter6_test_stub.h>
+#include <netinet/in.h>
+#include <list>
+
+namespace {
+
+/// @brief Retrieves a queue of sent messages.
+std::list<isc::dhcp::Pkt6Ptr>& getSent() {
+    static std::list<isc::dhcp::Pkt6Ptr> sent;
+    return (sent);
+}
+
+}
 
 namespace isc {
 namespace dhcp {
 namespace test {
 
 PktFilter6TestStub::PktFilter6TestStub() {
+    getSent().clear();
 }
 
 SocketInfo
 PktFilter6TestStub::openSocket(const Iface&,
            const isc::asiolink::IOAddress& addr,
            const uint16_t port, const bool) {
-    return (SocketInfo(addr, port, 0));
+    // Create a socket which may be used on select() but which is unlikely
+    // to return any data.
+    int sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+    if (sock < 0) {
+        isc_throw(Unexpected, "PktFilter6TestStub: opening socket failed");
+    }
+
+    // Set reuse address
+    int flag = 1;
+    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, static_cast<const void*>(&flag),
+                   sizeof(flag)) < 0) {
+        ::close(sock);
+        isc_throw(Unexpected, "PktFilter6TestStub:: failed to set SO_REUSEADDR on"
+                  " socket");
+    }
+
+    // Set no blocking
+    if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) {
+        ::close(sock);
+        isc_throw(Unexpected, "PktFilter6TestStub: failed to set O_NONBLOCK on"
+                  " socket");
+    }
+
+    // Bind to the local address and random port.
+    struct sockaddr_in6 local6;
+    memset(&local6, 0, sizeof(local6));
+    local6.sin6_family = AF_INET6;
+#ifdef HAVE_SA_LEN
+    local6.sin6_len = sizeof(local6);
+#endif
+
+    if (bind(sock, (struct sockaddr *)&local6, sizeof(local6)) < 0) {
+        ::close(sock);
+        isc_throw(Unexpected, "PktFilter6TestStub: failed to bind socket");
+    }
+
+    return (SocketInfo(addr, port, sock));
 }
 
 Pkt6Ptr
@@ -42,10 +91,22 @@ PktFilter6TestStub::joinMulticast(int, const std::string&,
 }
 
 int
-PktFilter6TestStub::send(const Iface&, uint16_t, const Pkt6Ptr&) {
+PktFilter6TestStub::send(const Iface&, uint16_t, const Pkt6Ptr& pkt) {
+    getSent().push_back(pkt);
     return (0);
 }
 
+Pkt6Ptr
+PktFilter6TestStub::popSent() {
+    std::list<Pkt6Ptr>& sent = getSent();
+    if (sent.empty()) {
+        return (Pkt6Ptr());
+    }
+    Pkt6Ptr pkt = sent.front();
+    sent.pop_front();
+    return (pkt);
+}
+
 }
 }
 }

+ 11 - 6
src/lib/dhcp/tests/pkt_filter6_test_stub.h

@@ -36,11 +36,10 @@ public:
     /// @brief Constructor.
     PktFilter6TestStub();
 
-    /// @brief Simulate opening of the socket.
+    /// @brief Open a socket.
     ///
-    /// This function simulates opening a primary socket. In reality, it doesn't
-    /// open a socket but the socket descriptor returned in the SocketInfo
-    /// structure is always set to 0.
+    /// This function opens a socket. This socket is bound to any address
+    /// and port. The select() function may be called on this socket.
     ///
     /// @param iface An interface descriptor.
     /// @param addr Address on the interface to be used to send packets.
@@ -50,8 +49,7 @@ public:
     ///
     /// @note All parameters are ignored.
     ///
-    /// @return A SocketInfo structure with the socket descriptor set to 0. The
-    /// fallback socket descriptor is set to a negative value.
+    /// @return A SocketInfo structure with the socket descriptor.
     virtual SocketInfo openSocket(const Iface& iface,
                                   const isc::asiolink::IOAddress& addr,
                                   const uint16_t port,
@@ -90,6 +88,13 @@ public:
     /// @return true if multicast join was successful
     static bool joinMulticast(int sock, const std::string& ifname,
                               const std::string & mcast);
+
+    /// @brief Retrieve next sent message.
+    ///
+    /// @return Pointer to the next sent message or NULL if there are no
+    /// more messages.
+    static Pkt6Ptr popSent();
+
 };
 
 } // namespace isc::dhcp::test

+ 1 - 0
src/lib/dhcpsrv/Makefile.am

@@ -104,6 +104,7 @@ libkea_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.h
 libkea_dhcpsrv_la_SOURCES += daemon.cc daemon.h
 libkea_dhcpsrv_la_SOURCES += database_connection.cc database_connection.h
 libkea_dhcpsrv_la_SOURCES += db_exceptions.h
+libkea_dhcpsrv_la_SOURCES += dhcp4o6_ipc.cc dhcp4o6_ipc.h
 libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libkea_dhcpsrv_la_SOURCES += host.cc host.h
 libkea_dhcpsrv_la_SOURCES += host_container.h

+ 266 - 0
src/lib/dhcpsrv/dhcp4o6_ipc.cc

@@ -0,0 +1,266 @@
+// Copyright (C) 2015 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/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 <boost/pointer_cast.hpp>
+#include <errno.h>
+#include <netinet/in.h>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+Dhcp4o6IpcBase::Dhcp4o6IpcBase() : port_(0), socket_fd_(-1) {}
+
+Dhcp4o6IpcBase::~Dhcp4o6IpcBase() {
+    close();
+}
+
+int Dhcp4o6IpcBase::open(const uint16_t port, const EndpointType& endpoint_type) {
+    // Check if the port value is correct.
+    if (port > 65534) {
+        isc_throw(Dhcp4o6IpcError, "specified port " << port << " is out of"
+                  " range. The port value must not be greater than 65534 ");
+    }
+
+    if (port == port_) {
+        // No change: nothing to do
+        return (socket_fd_);
+    }
+
+    // Open socket
+    int sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+    if (sock < 0) {
+        isc_throw(Dhcp4o6IpcError, "Failed to create DHCP4o6 socket.");
+    }
+
+    // Set reuse address
+    int flag = 1;
+    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, static_cast<const void*>(&flag),
+                   sizeof(flag)) < 0) {
+        ::close(sock);
+        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(Dhcp4o6IpcError, "Failed to set O_NONBLOCK on DHCP4o6 socket.");
+    }
+
+    // Bind to the local address
+    struct sockaddr_in6 local6;
+    memset(&local6, 0, sizeof(local6));
+    local6.sin6_family = AF_INET6;
+#ifdef HAVE_SA_LEN
+    local6.sin6_len = sizeof(local6);
+#endif
+    if (endpoint_type == ENDPOINT_TYPE_V6) {
+        local6.sin6_port = htons(port);
+    } else {
+        local6.sin6_port = htons(port + 1);
+    }
+    if (bind(sock, (struct sockaddr *)&local6, sizeof(local6)) < 0) {
+        ::close(sock);
+        isc_throw(Dhcp4o6IpcError, "Failed to bind DHCP4o6 socket.");
+    }
+
+    // Connect to the remote address
+    struct sockaddr_in6 remote6;
+    memset(&remote6, 0, sizeof(remote6));
+    remote6.sin6_family = AF_INET6;
+#ifdef HAVE_SA_LEN
+    remote6.sin6_len = sizeof(remote6);
+#endif
+    if (endpoint_type == ENDPOINT_TYPE_V6) {
+        remote6.sin6_port = htons(port + 1);
+    } else {
+        remote6.sin6_port = htons(port);
+    }
+    if (connect(sock, reinterpret_cast<const struct sockaddr*>(&remote6),
+                sizeof(remote6)) < 0) {
+        ::close(sock);
+        isc_throw(Dhcp4o6IpcError, "Failed to connect DHCP4o6 socket.");
+    }
+
+    if (socket_fd_ != -1) {
+        if (dup2(sock, socket_fd_) == -1) {
+            ::close(sock);
+            isc_throw(Dhcp4o6IpcError, "Failed to duplicate DHCP4o6 socket.");
+        }
+        if (sock != socket_fd_) {
+            ::close(sock);
+            sock = socket_fd_;
+        }
+    }
+
+    // Success
+    port_ = port;
+    socket_fd_ = sock;
+    return (socket_fd_);
+}
+
+void Dhcp4o6IpcBase::close() {
+    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(Dhcp4o6IpcError, "Failed to receive on DHCP4o6 socket.");
+    }
+    Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(buf, cc));
+    pkt->updateTimestamp();
+
+    // Get interface name and remote address
+    pkt->unpack();
+
+    // 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;
+            }
+
+        } else {
+            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) {
+        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) {
+        isc_throw(Dhcp4o6IpcError, "option " << ISC_V6_4O6_INTERFACE
+                  << " sent in the DHCP4o6 message contains non-existing"
+                  " interface name '" << ifname->getValue() << "'");
+    }
+
+    // Get the option holding source IPv6 address.
+    OptionCustomPtr srcs = boost::dynamic_pointer_cast<
+        OptionCustom>(option_vendor->getOption(ISC_V6_4O6_SRC_ADDRESS));
+    if (!srcs) {
+        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(const Pkt6Ptr& pkt) {
+    // This shouldn't happen, i.e. send() shouldn't be called if there is
+    // no message.
+    if (!pkt) {
+        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) {
+        isc_throw(Dhcp4o6IpcError, "unable to send DHCP4o6 message because"
+                  " IPC socket is closed");
+    }
+
+    // Check if vendor option exists.
+    OptionVendorPtr option_vendor = 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 (!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
+    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())));
+    // Get packet content
+    OutputBuffer& buf = pkt->getBuffer();
+    buf.clear();
+    pkt->pack();
+
+    // 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
+
+};  // namespace isc

+ 134 - 0
src/lib/dhcpsrv/dhcp4o6_ipc.h

@@ -0,0 +1,134 @@
+// Copyright (C) 2015 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_IPC_H
+#define DHCP4O6_IPC_H
+
+/// @file dhcp4o6_ipc.h Defines the Dhcp4o6IpcBase class.
+/// This file defines the class Kea uses as a base for
+/// DHCPv4-over-DHCPv6 communication between servers.
+///
+
+#include <exceptions/exceptions.h>
+#include <dhcp/pkt6.h>
+#include <boost/noncopyable.hpp>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @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,
+        ENDPOINT_TYPE_V6
+    };
+
+protected:
+    /// @brief Constructor
+    ///
+    /// Default constructor
+    Dhcp4o6IpcBase();
+
+    /// @brief Destructor.
+    virtual ~Dhcp4o6IpcBase();
+
+    /// @brief Open communication socket (from base class).
+    ///
+    /// @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(const uint16_t port, const EndpointType& endpoint_type);
+
+public:
+
+    /// @brief Open communication socket (for derived classes).
+    virtual void open() = 0;
+
+    /// @brief Close communication socket.
+    void close();
+
+    /// @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 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 pkt Pointer to a DHCPv6 message with interface and remote
+    /// address.
+    void send(const Pkt6Ptr& pkt);
+
+protected:
+
+    /// @brief Port number configured for IPC communication.
+    uint16_t port_;
+
+    /// @brief Socket descriptor.
+    int socket_fd_;
+};
+
+} // namespace isc
+} // namespace dhcp
+
+#endif

+ 2 - 2
src/lib/dhcpsrv/srv_config.cc

@@ -33,7 +33,7 @@ SrvConfig::SrvConfig()
       cfg_hosts_(new CfgHosts()), cfg_rsoo_(new CfgRSOO()),
       cfg_expiration_(new CfgExpiration()), cfg_duid_(new CfgDUID()),
       class_dictionary_(new ClientClassDictionary()),
-      decline_timer_(0) {
+      decline_timer_(0), dhcp4o6_port_(0) {
 }
 
 SrvConfig::SrvConfig(const uint32_t sequence)
@@ -43,7 +43,7 @@ SrvConfig::SrvConfig(const uint32_t sequence)
       cfg_hosts_(new CfgHosts()), cfg_rsoo_(new CfgRSOO()),
       cfg_expiration_(new CfgExpiration()), cfg_duid_(new CfgDUID()),
       class_dictionary_(new ClientClassDictionary()),
-      decline_timer_(0) {
+      decline_timer_(0), dhcp4o6_port_(0) {
 }
 
 std::string

+ 24 - 0
src/lib/dhcpsrv/srv_config.h

@@ -439,6 +439,24 @@ public:
         return (decline_timer_);
     }
 
+    /// @brief Sets DHCP4o6 IPC port
+    ///
+    /// DHCPv4-over-DHCPv6 uses a UDP socket for interserver communication,
+    /// this socket is bound and connected to this port and port + 1
+    ///
+    /// @param port port and port + 1 to use
+    void setDhcp4o6Port(uint32_t port) {
+        dhcp4o6_port_ = port;
+    }
+
+    /// @brief Returns DHCP4o6 IPC port
+    ///
+    /// See @ref setDhcp4o6Port or brief discussion.                         
+    /// @return value of DHCP4o6 IPC port
+    uint32_t getDhcp4o6Port() {
+        return (dhcp4o6_port_);
+    }
+
 private:
 
     /// @brief Sequence number identifying the configuration.
@@ -504,6 +522,12 @@ private:
     /// This timer specifies decline probation period, the time after a declined
     /// lease is recovered back to available state. Expressed in seconds.
     uint32_t decline_timer_;
+
+    /// @brief DHCP4o6 IPC port
+    ///
+    /// DHCPv4-over-DHCPv6 uses a UDP socket for interserver communication,
+    /// this socket is bound and connected to this port and port + 1
+    uint32_t dhcp4o6_port_;
 };
 
 /// @name Pointers to the @c SrvConfig object.

+ 1 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -85,6 +85,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

+ 577 - 0
src/lib/dhcpsrv/tests/dhcp4o6_ipc_unittest.cc

@@ -0,0 +1,577 @@
+// Copyright (C) 2015 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 <dhcp/option_vendor.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
+#include <dhcpsrv/dhcp4o6_ipc.h>
+#include <dhcpsrv/testutils/dhcp4o6_test_ipc.h>
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test port.
+const uint16_t TEST_PORT = 12345;
+
+/// @brief Number of iterations used by the tests.
+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;
+
+/// @brief Define short name for test IPC class.
+typedef Dhcp4o6TestIpc TestIpc;
+
+/// @brief Test fixture class for @c Dhcp4o6IpcBase.
+class Dhcp4o6IpcBaseTest : public ::testing::Test {
+protected:
+
+    /// @brief Constructor.
+    ///
+    /// Replaces the real configuration of interfaces with a fake configuration.
+    /// The IPC uses the @c IfaceMgr to check whether the interfaces which names
+    /// are carried within DHCP options exist in the system. Providing the fake
+    /// configuration for the @c IfaceMgr guarantees that the configuration is
+    /// consistent on any machine running the unit tests.
+    Dhcp4o6IpcBaseTest();
+
+    /// @brief Concatenates the prefix and postfix.
+    ///
+    /// @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);
+
+    /// @brief Creates an instance of the DHCPv4o6 message.
+    ////
+    /// @param msg_type Message type.
+    /// @param postfix Postfix to be appended to the remote address. For example,
+    /// for postfix = 5 the resulting remote address will be 2001:db8:1::5.
+    /// The postifx value is also used to generate the postfix for the interface.
+    /// The possible interface names are "eth0" and "eth1". For even postfix values
+    /// 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);
+
+    /// @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.
+    ///
+    /// @return Pointer to the created message.
+    static Pkt6Ptr createDHCPv4o6MsgWithVendorOption(const uint16_t msg_type,
+                                                     const uint16_t postfix,
+                                                     const uint32_t enterprise_id);
+
+    /// @brief Creates an instance of the DHCPv4o6 message with ISC
+    /// vendor option.
+    ///
+    /// This is useful to test scenarios when the IPC is forwarding messages
+    /// that contain vendor option with ISC enterprise ID.
+    ///
+    /// @param msg_type Message type.
+    /// @param postfix Postfix to be appended to the remote address. See the
+    /// documentation of @c createDHCPv4o6Message for details.
+    ///
+    /// @return Pointer to the created message.
+    static Pkt6Ptr createDHCPv4o6MsgWithISCVendorOption(const uint16_t msg_type,
+                                                        const uint16_t postfix);
+
+    /// @brief Creates an instance of the DHCPv4o6 message with vendor
+    /// option holding enterprise id of 32000.
+    ///
+    /// This is useful to test scenarios when the IPC is forwarding messages
+    /// that contain some vendor option and when IPC also appends the ISC
+    /// vendor option to carry some DHCPv4o6 specific information.
+    ///
+    /// @param msg_type Message type.
+    /// @param postfix Postfix to be appended to the remote address. See the
+    /// documentation of @c createDHCPv4o6Message for details.
+    ///
+    /// @return Pointer to the created message.
+    static Pkt6Ptr createDHCPv4o6MsgWithAnyVendorOption(const uint16_t msg_type,
+                                                        const 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);
+
+    /// @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,
+                         const CreateMsgFun& create_msg_fun);
+
+    /// @brief Tests that error is reported when invalid message is received.
+    ///
+    /// @param pkt Pointer to the invalid message.
+    void testReceiveError(const Pkt6Ptr& pkt);
+
+private:
+
+    /// @brief Holds the fake configuration of the interfaces.
+    IfaceMgrTestConfig iface_mgr_test_config_;
+
+};
+
+Dhcp4o6IpcBaseTest::Dhcp4o6IpcBaseTest()
+    : iface_mgr_test_config_(true) {
+}
+
+std::string
+Dhcp4o6IpcBaseTest::concatenate(const std::string& prefix,
+                                const uint16_t postfix) {
+    std::ostringstream s;
+    s << prefix << postfix;
+    return (s.str());
+}
+
+Pkt6Ptr
+Dhcp4o6IpcBaseTest::createDHCPv4o6Message(const uint16_t msg_type,
+                                          const uint16_t postfix) {
+    // Create the DHCPv4o6 message.
+    Pkt6Ptr pkt(new Pkt6(msg_type, 0));
+
+    // The interface name is carried in the dedicated option between
+    // the servers. The receiving server will check that such interface
+    // is present in the system. The fake configuration we're using for
+    // this test includes two interfaces: "eth0" and "eth1". Therefore,
+    // we pick one or another, depending on the index of the interation.
+    pkt->setIface(concatenate("eth", postfix % 2));
+
+    // The remote address of the sender of the DHCPv6 packet is carried
+    // between the servers in the dedicated option. We use different
+    // address for each iteration to make sure that the IPC delivers the
+    // right address.
+    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::ENDPOINT_TYPE_V6 : TestIpc::ENDPOINT_TYPE_V4;
+
+    // Add DHCPv4 Message option to make sure it is conveyed by the IPC.
+    pkt->addOption(createDHCPv4MsgOption(src));
+
+    return (pkt);
+}
+
+Pkt6Ptr
+Dhcp4o6IpcBaseTest::createDHCPv4o6MsgWithVendorOption(const uint16_t msg_type,
+                                                      const uint16_t postfix,
+                                                      const uint32_t enterprise_id) {
+    Pkt6Ptr pkt = createDHCPv4o6Message(msg_type, postfix);
+
+    // Create vendor option with ISC enterprise id.
+    OptionVendorPtr option_vendor(new OptionVendor(Option::V6, enterprise_id));
+
+    // Add some option to the vendor option.
+    option_vendor->addOption(OptionPtr(new Option(Option::V6, 100)));
+
+    // Add vendor option to the message.
+    pkt->addOption(option_vendor);
+
+    return (pkt);
+}
+
+Pkt6Ptr
+Dhcp4o6IpcBaseTest::createDHCPv4o6MsgWithISCVendorOption(const uint16_t msg_type,
+                                                         const uint16_t postfix) {
+    return (createDHCPv4o6MsgWithVendorOption(msg_type, postfix, ENTERPRISE_ID_ISC));
+}
+
+Pkt6Ptr
+Dhcp4o6IpcBaseTest::createDHCPv4o6MsgWithAnyVendorOption(const uint16_t msg_type,
+                                                         const uint16_t postfix) {
+    return (createDHCPv4o6MsgWithVendorOption(msg_type, postfix, 32000));
+}
+
+OptionPtr
+Dhcp4o6IpcBaseTest::createDHCPv4MsgOption(const TestIpc::EndpointType& src) {
+    // Create the DHCPv4 message.
+    Pkt4Ptr pkt(new Pkt4(src == TestIpc::ENDPOINT_TYPE_V4 ? DHCPACK : 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);
+}
+
+void
+Dhcp4o6IpcBaseTest::testSendReceive(const uint16_t iterations_num,
+                                    const TestIpc::EndpointType& src,
+                                    const TestIpc::EndpointType& dest,
+                                    const CreateMsgFun& create_msg_fun) {
+    // Create IPC instances representing the source and destination endpoints.
+    TestIpc ipc_src(TEST_PORT, src);
+    TestIpc ipc_dest(TEST_PORT, dest);
+
+    // Open the IPC on both ends.
+    ASSERT_NO_THROW(ipc_src.open());
+    ASSERT_NO_THROW(ipc_dest.open());
+
+    // Depnding 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 :
+                         DHCPV6_DHCPV4_RESPONSE);
+
+    std::vector<bool> has_vendor_option;
+
+    // Send the number of messages configured for the test.
+    for (uint16_t i = 1; i <= iterations_num; ++i) {
+        // Create the DHCPv4o6 message.
+        Pkt6Ptr pkt = create_msg_fun(msg_type, i);
+
+        // Remember if the vendor option exists in the source packet. The
+        // received packet should also contain this option if it exists
+        // in the source packet.
+        has_vendor_option.push_back(static_cast<bool>(pkt->getOption(D6O_VENDOR_OPTS)));
+
+        // Actaully send the message through the IPC.
+        ASSERT_NO_THROW(ipc_src.send(pkt))
+            << "Failed to send the message over the IPC for iteration " << i;
+    }
+
+    // Try to receive all messages.
+    for (uint16_t i = 1; i <= iterations_num; ++i) {
+
+        // Call receive with a timeout. The data should appear on the socket
+        // within this time.
+        ASSERT_NO_THROW(IfaceMgr::instance().receive6(1, 0));
+
+        // Pop the received message.
+        Pkt6Ptr pkt_received = ipc_dest.popPktReceived();
+        ASSERT_TRUE(pkt_received);
+
+        // Check that the message type is correct.
+        EXPECT_EQ(msg_type, pkt_received->getType());
+
+        // Check that the interface is correct.
+        EXPECT_EQ(concatenate("eth", i % 2), pkt_received->getIface());
+
+        // Check that the address conveyed is correct.
+        EXPECT_EQ(concatenate("2001:db8:1::", i),
+                  pkt_received->getRemoteAddr().toText());
+
+        // Check that encapsulated DHCPv4 message has been received.
+        EXPECT_TRUE(pkt_received->getOption(D6O_DHCPV4_MSG));
+
+        if (has_vendor_option[i - 1]) {
+            // Make sure that the vendor option wasn't deleted when the packet was
+            // received.
+            OptionPtr option_vendor = pkt_received->getOption(D6O_VENDOR_OPTS);
+            ASSERT_TRUE(option_vendor)
+                << "vendor option deleted in the received DHCPv4o6 packet for"
+                " iteration " << i;
+
+            // ISC_V6_4O6_INTERFACE shouldn't be present.
+            EXPECT_FALSE(option_vendor->getOption(ISC_V6_4O6_INTERFACE));
+
+            // ISC_V6_4O6_SRC_ADDRESS shouldn't be present.
+            EXPECT_FALSE(option_vendor->getOption(ISC_V6_4O6_SRC_ADDRESS));
+
+        } else {
+            EXPECT_FALSE(pkt_received->getOption(D6O_VENDOR_OPTS));
+        }
+    }
+}
+
+void
+Dhcp4o6IpcBaseTest::testReceiveError(const Pkt6Ptr& pkt) {
+    TestIpc ipc_src(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6);
+    TestIpc ipc_dest(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4);
+
+    // Open the IPC on both ends.
+    ASSERT_NO_THROW(ipc_src.open());
+    ASSERT_NO_THROW(ipc_dest.open());
+
+    pkt->setIface("eth0");
+    pkt->setRemoteAddr(IOAddress("2001:db8:1::1"));
+    pkt->addOption(createDHCPv4MsgOption(TestIpc::ENDPOINT_TYPE_V6));
+
+    OutputBuffer& buf = pkt->getBuffer();
+    buf.clear();
+    ASSERT_NO_THROW(pkt->pack());
+
+    ASSERT_NE(-1, ::send(ipc_src.getSocketFd(), buf.getData(),
+                         buf.getLength(), 0));
+
+    // Call receive with a timeout. The data should appear on the socket
+    // within this time.
+    ASSERT_THROW(IfaceMgr::instance().receive6(1, 0), Dhcp4o6IpcError);
+}
+
+
+// This test verifies that the IPC can transmit messages between the
+// DHCPv4 and DHCPv6 server.
+TEST_F(Dhcp4o6IpcBaseTest, send4To6) {
+    testSendReceive(TEST_ITERATIONS, TestIpc::ENDPOINT_TYPE_V4,
+                    TestIpc::ENDPOINT_TYPE_V6, &createDHCPv4o6Message);
+}
+
+// This test verifies that the IPC can transmit messages between the
+// DHCPv6 and DHCPv4 server.
+TEST_F(Dhcp4o6IpcBaseTest, send6To4) {
+    testSendReceive(TEST_ITERATIONS, TestIpc::ENDPOINT_TYPE_V6,
+                    TestIpc::ENDPOINT_TYPE_V4, &createDHCPv4o6Message);
+}
+
+// This test verifies that the IPC will transmit message already containing
+// vendor option with ISC enterprise ID, between the DHCPv6 and DHCPv4
+// server.
+TEST_F(Dhcp4o6IpcBaseTest, send6To4WithISCVendorOption) {
+    testSendReceive(TEST_ITERATIONS, TestIpc::ENDPOINT_TYPE_V6,
+                    TestIpc::ENDPOINT_TYPE_V4,
+                    &createDHCPv4o6MsgWithISCVendorOption);
+}
+
+// This test verifies that the IPC will transmit message already containing
+// vendor option with ISC enterprise ID, between the DHCPv6 and DHCPv4
+// server.
+TEST_F(Dhcp4o6IpcBaseTest, send4To6WithISCVendorOption) {
+    testSendReceive(TEST_ITERATIONS, TestIpc::ENDPOINT_TYPE_V4,
+                    TestIpc::ENDPOINT_TYPE_V6,
+                    &createDHCPv4o6MsgWithISCVendorOption);
+}
+
+// This test verifies that the IPC will transmit message already containing
+// vendor option with enterprise id different than ISC, between the DHCPv6
+// and DHCPv4 server.
+TEST_F(Dhcp4o6IpcBaseTest, send6To4WithAnyVendorOption) {
+    testSendReceive(TEST_ITERATIONS, TestIpc::ENDPOINT_TYPE_V6,
+                    TestIpc::ENDPOINT_TYPE_V4,
+                    &createDHCPv4o6MsgWithAnyVendorOption);
+}
+
+// This test verifies that the IPC will transmit message already containing
+// vendor option with enterprise id different than ISC, between the DHCPv4
+// and DHCPv6 server.
+TEST_F(Dhcp4o6IpcBaseTest, send4To6WithAnyVendorOption) {
+    testSendReceive(TEST_ITERATIONS, TestIpc::ENDPOINT_TYPE_V4,
+                    TestIpc::ENDPOINT_TYPE_V6,
+                    &createDHCPv4o6MsgWithAnyVendorOption);
+}
+
+// This test checks that the values of the socket descriptor are correct
+// when the socket is opened and then closed.
+TEST_F(Dhcp4o6IpcBaseTest, openClose) {
+    TestIpc ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4);
+    EXPECT_EQ(-1, ipc.getSocketFd());
+
+    ASSERT_NO_THROW(ipc.open());
+    EXPECT_NE(-1, ipc.getSocketFd());
+
+    ASSERT_NO_THROW(ipc.close());
+    EXPECT_EQ(-1, ipc.getSocketFd());
+}
+
+// This test verifies that it is call open() while the socket is already
+// opened. If the port changes, the new socket should be opened.
+TEST_F(Dhcp4o6IpcBaseTest, openMultipleTimes) {
+   TestIpc ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6);
+   ASSERT_NO_THROW(ipc.open());
+   int sock_fd = ipc.getSocketFd();
+   ASSERT_NE(-1, sock_fd);
+   ASSERT_EQ(TEST_PORT, ipc.getPort());
+
+   ASSERT_NO_THROW(ipc.open());
+   ASSERT_EQ(sock_fd, ipc.getSocketFd());
+   ASSERT_EQ(TEST_PORT, ipc.getPort());
+
+   ipc.setDesiredPort(TEST_PORT + 10);
+   ASSERT_NO_THROW(ipc.open());
+   ASSERT_NE(-1, ipc.getSocketFd());
+
+   EXPECT_EQ(TEST_PORT + 10, ipc.getPort());
+}
+
+// This test verifies that the socket remains open if there is a failure
+// to open a new socket.
+TEST_F(Dhcp4o6IpcBaseTest, openError) {
+    TestIpc ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4);
+    ASSERT_NO_THROW(ipc.open());
+    int sock_fd = ipc.getSocketFd();
+    ASSERT_NE(-1, sock_fd);
+
+    TestIpc ipc_bound(TEST_PORT + 10, TestIpc::ENDPOINT_TYPE_V4);
+    ASSERT_NO_THROW(ipc_bound.open());
+    ASSERT_NE(-1, ipc_bound.getSocketFd());
+
+    ipc.setDesiredPort(TEST_PORT + 10);
+    ASSERT_THROW(ipc.open(), isc::dhcp::Dhcp4o6IpcError);
+
+    EXPECT_EQ(sock_fd, ipc.getSocketFd());
+    EXPECT_EQ(TEST_PORT, ipc.getPort());
+
+    ASSERT_NO_THROW(ipc_bound.close());
+    ASSERT_NO_THROW(ipc.open());
+    EXPECT_NE(-1, ipc.getSocketFd());
+    EXPECT_EQ(TEST_PORT + 10, ipc.getPort());
+}
+
+// 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);
+}
+
+// This test verifies that receiving packet over the IPC fails when there
+// is no vendor option present.
+TEST_F(Dhcp4o6IpcBaseTest, receiveWithoutVendorOption) {
+    Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0));
+    testReceiveError(pkt);
+}
+
+// This test verifies that receving packet over the IPC fails when the
+// enterprise ID carried in the vendor option is invalid.
+TEST_F(Dhcp4o6IpcBaseTest, receiveInvalidEnterpriseId) {
+    Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0));
+    OptionVendorPtr option_vendor(new OptionVendor(Option::V6, 1));
+    option_vendor->addOption(
+        OptionStringPtr(new OptionString(Option::V6, ISC_V6_4O6_INTERFACE,
+                                         "eth0")));
+    option_vendor->addOption(
+        Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
+                                             IOAddress("2001:db8:1::1")))
+    );
+
+    pkt->addOption(option_vendor);
+    testReceiveError(pkt);
+}
+
+// This test verifies that receiving packet over the IPC fails when the
+// interface option is not present.
+TEST_F(Dhcp4o6IpcBaseTest, receiveWithoutInterfaceOption) {
+    Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0));
+    OptionVendorPtr option_vendor(new OptionVendor(Option::V6,
+                                                   ENTERPRISE_ID_ISC));
+    option_vendor->addOption(
+        Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
+                                             IOAddress("2001:db8:1::1")))
+    );
+
+    pkt->addOption(option_vendor);
+    testReceiveError(pkt);
+}
+
+// This test verifies that receiving packet over the IPC fails when the
+// interface which name is carried in the option is not present in the
+// system.
+TEST_F(Dhcp4o6IpcBaseTest, receiveWithInvalidInterface) {
+    Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0));
+    OptionVendorPtr option_vendor(new OptionVendor(Option::V6,
+                                                   ENTERPRISE_ID_ISC));
+    option_vendor->addOption(
+        OptionStringPtr(new OptionString(Option::V6, ISC_V6_4O6_INTERFACE,
+                                         "ethX")));
+    option_vendor->addOption(
+        Option6AddrLstPtr(new Option6AddrLst(ISC_V6_4O6_SRC_ADDRESS,
+                                             IOAddress("2001:db8:1::1")))
+    );
+
+    pkt->addOption(option_vendor);
+    testReceiveError(pkt);
+}
+
+
+// This test verifies that receving packet over the IPC fails when the
+// source address option is not present.
+TEST_F(Dhcp4o6IpcBaseTest, receiveWithoutSourceAddressOption) {
+    Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 0));
+    OptionVendorPtr option_vendor(new OptionVendor(Option::V6,
+                                                   ENTERPRISE_ID_ISC));
+    option_vendor->addOption(
+        OptionStringPtr(new OptionString(Option::V6, ISC_V6_4O6_INTERFACE,
+                                         "eth0")));
+
+    pkt->addOption(option_vendor);
+    testReceiveError(pkt);
+}
+
+// This test verifies that send method throws exception when the packet
+// is NULL.
+TEST_F(Dhcp4o6IpcBaseTest, sendNullMessage) {
+    TestIpc ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4);
+    ASSERT_NO_THROW(ipc.open());
+
+    // NULL message.
+    EXPECT_THROW(ipc.send(Pkt6Ptr()), Dhcp4o6IpcError);
+}
+
+// This test verifies that send method throws exception when the IPC
+// socket is not opened.
+TEST_F(Dhcp4o6IpcBaseTest, sendOverClosedSocket) {
+    TestIpc ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4);
+
+    // Create a message.
+    Pkt6Ptr pkt(createDHCPv4o6Message(DHCPV6_DHCPV4_QUERY));
+
+    // Sending over the closed socket should fail.
+    EXPECT_THROW(ipc.send(pkt), Dhcp4o6IpcError);
+}
+
+// This test verifies that send method throws exception when the IPC
+// socket has been unexpectedly closed.
+TEST_F(Dhcp4o6IpcBaseTest, sendOverUnexpectedlyClosedSocket) {
+    TestIpc ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V4);
+    ASSERT_NO_THROW(ipc.open());
+
+    // Close the socket behind the scenes. The IPC doesn't know that the
+    // socket has been closed and it still holds the descriptor.
+    ::close(ipc.getSocketFd());
+
+    // Create a message.
+    Pkt6Ptr pkt(createDHCPv4o6Message(DHCPV6_DHCPV4_QUERY));
+
+    // Sending over the closed socket should fail.
+    EXPECT_THROW(ipc.send(pkt), Dhcp4o6IpcError);
+}
+
+} // end of anonymous namespace

+ 2 - 0
src/lib/dhcpsrv/testutils/Makefile.am

@@ -16,6 +16,8 @@ if HAVE_GTEST
 noinst_LTLIBRARIES = libdhcpsrvtest.la
 
 libdhcpsrvtest_la_SOURCES  = config_result_check.cc config_result_check.h
+libdhcpsrvtest_la_SOURCES += dhcp4o6_test_ipc.cc dhcp4o6_test_ipc.h
+
 libdhcpsrvtest_la_CXXFLAGS = $(AM_CXXFLAGS)
 libdhcpsrvtest_la_CPPFLAGS = $(AM_CPPFLAGS)
 libdhcpsrvtest_la_LDFLAGS  = $(AM_LDFLAGS)

+ 50 - 0
src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.cc

@@ -0,0 +1,50 @@
+// Copyright (C) 2015 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(const uint16_t port,
+                               const 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

+ 100 - 0
src/lib/dhcpsrv/testutils/dhcp4o6_test_ipc.h

@@ -0,0 +1,100 @@
+// Copyright (C) 2015 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(const uint16_t port, const EndpointType& endpoint_type);
+
+    /// @brief Sets new port to be used with @c open.
+    ///
+    /// @param desired_port New desired port.
+    void setDesiredPort(const 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