]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[fd4o6] Implemented DHCPv4-over-DHCPv6 with ISC DHCP compatible IPC (checkpoint)
authorFrancis Dupont <fdupont@isc.org>
Fri, 18 Sep 2015 05:37:14 +0000 (07:37 +0200)
committerFrancis Dupont <fdupont@isc.org>
Fri, 18 Sep 2015 05:37:14 +0000 (07:37 +0200)
24 files changed:
src/bin/dhcp4/Makefile.am
src/bin/dhcp4/ctrl_dhcp4_srv.cc
src/bin/dhcp4/dhcp4_dhcp4o6_ipc.cc [new file with mode: 0644]
src/bin/dhcp4/dhcp4_dhcp4o6_ipc.h [new file with mode: 0644]
src/bin/dhcp4/dhcp4_messages.mes
src/bin/dhcp4/dhcp4_srv.cc
src/bin/dhcp4/dhcp4_srv.h
src/bin/dhcp4/json_config_parser.cc
src/bin/dhcp6/Makefile.am
src/bin/dhcp6/ctrl_dhcp6_srv.cc
src/bin/dhcp6/dhcp6_dhcp4o6_ipc.cc [new file with mode: 0644]
src/bin/dhcp6/dhcp6_dhcp4o6_ipc.h [new file with mode: 0644]
src/bin/dhcp6/dhcp6_srv.cc
src/bin/dhcp6/dhcp6_srv.h
src/bin/dhcp6/json_config_parser.cc
src/lib/dhcp/pkt4.h
src/lib/dhcp/pkt4o6.cc
src/lib/dhcp/pkt4o6.h
src/lib/dhcp/tests/libdhcp++_unittest.cc
src/lib/dhcpsrv/Makefile.am
src/lib/dhcpsrv/dhcp4o6_ipc.cc [new file with mode: 0644]
src/lib/dhcpsrv/dhcp4o6_ipc.h [new file with mode: 0644]
src/lib/dhcpsrv/srv_config.cc
src/lib/dhcpsrv/srv_config.h

index 2a3810049efb8eb4a23c1359efaf5985d3d4a8be..bcfa2abdbcda34d28ec3efba83790d681a3e77af 100644 (file)
@@ -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
 
index c9abff19a319a10b82ad19d6512446e309312607..b5f8230e6ecbb4326b250d77d95ea1c3fcc062da 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-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
@@ -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>
@@ -147,6 +148,16 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
         return (isc::config::createAnswer(1, err.str()));
     }
 
+    // Setup DHCPv4-over-DHCPv6 IPC
+    try {
+        Dhcp4o6Ipc::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
diff --git a/src/bin/dhcp4/dhcp4_dhcp4o6_ipc.cc b/src/bin/dhcp4/dhcp4_dhcp4o6_ipc.cc
new file mode 100644 (file)
index 0000000..f5fb503
--- /dev/null
@@ -0,0 +1,76 @@
+// 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 {
+
+Dhcp4o6Ipc::Dhcp4o6Ipc() : Dhcp4o6IpcBase() {}
+
+Dhcp4o6Ipc& Dhcp4o6Ipc::instance() {
+    static Dhcp4o6Ipc dhcp4o6_ipc;
+    return (dhcp4o6_ipc);
+}
+
+void Dhcp4o6Ipc::open() {
+    uint32_t port = CfgMgr::instance().getStagingCfg()->getDhcp4o6Port();
+    if (port == 0) {
+        Dhcp4o6IpcBase::close();
+        return;
+    }
+    if (port > 65534) {
+        isc_throw(OutOfRange, "DHCP4o6 port " << port);
+    }
+
+    int old_fd = socket_fd_;
+    socket_fd_ = Dhcp4o6IpcBase::open(static_cast<uint16_t>(port), 6);
+    if ((old_fd == -1) && (socket_fd_ != old_fd)) {
+        IfaceMgr::instance().addExternalSocket(socket_fd_, Dhcp4o6Ipc::handler);
+    }
+}
+
+void Dhcp4o6Ipc::handler() {
+    Dhcp4o6Ipc& ipc = Dhcp4o6Ipc::instance();
+    Pkt6Ptr pkt = ipc.receive();
+    if (!pkt) {
+        return;
+    }
+
+    pkt->unpack();
+    OptionCollection msgs = pkt->getOptions(D6O_DHCPV4_MSG);
+    if (msgs.size() != 1) {
+        return;
+    }
+    OptionPtr msg = pkt->getOption(D6O_DHCPV4_MSG);
+    if (!msg) {
+        isc_throw(Unexpected, "Can't get DHCPv4 message option");
+    }
+    instance().received_.reset(new Pkt4o6(msg->getData(), pkt));
+}
+
+Pkt4o6Ptr& Dhcp4o6Ipc::getReceived() {
+    return (received_);
+}
+
+};  // namespace dhcp
+
+};  // namespace isc
diff --git a/src/bin/dhcp4/dhcp4_dhcp4o6_ipc.h b/src/bin/dhcp4/dhcp4_dhcp4o6_ipc.h
new file mode 100644 (file)
index 0000000..5763e12
--- /dev/null
@@ -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 Dhcp4o6Ipc : public Dhcp4o6IpcBase {
+protected:
+    /// @brief Constructor
+    ///
+    /// Default constructor
+    Dhcp4o6Ipc();
+
+    /// @brief Destructor.
+    virtual ~Dhcp4o6Ipc() { }
+
+public:
+    /// @brief Returns pointer to the sole instance of Dhcp4o6Ipc
+    ///
+    /// Dhcp4o6Ipc is a singleton class
+    ///
+    /// @return the only existing instance of DHCP4o6 IPC
+    static Dhcp4o6Ipc& 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
index 7808186894094612ec2046cb4cad35c968b3e6aa..3e318cb1bb87caaf6c9ca7edb4eed192ed5c637c 100644 (file)
@@ -149,6 +149,10 @@ server will continue to use an old configuration.
 This is an informational message reporting that the configuration has
 been extended to include the specified IPv4 subnet.
 
+% DHCP4_CONFIG_NEW_SUBNET6 a new subnet has been added to configuration: %1
+This is an informational message reporting that the configuration has
+been extended to include the specified IPv6 subnet.
+
 % DHCP4_CONFIG_OPTION_DUPLICATE multiple options with the code %1 added to the subnet %2
 This warning message is issued on an attempt to configure multiple options
 with the same option code for a particular subnet. Adding multiple options
index 081539ccaabc99b3dff6d57b2ec7eefbcd3dd0f7..1d935da7f2e8a6588762beca26f18a69f50d43af 100644 (file)
 #include <dhcp/option_vendor.h>
 #include <dhcp/option_string.h>
 #include <dhcp/pkt4.h>
+#include <dhcp/pkt4o6.h>
+#include <dhcp/pkt6.h>
 #include <dhcp/docsis3_option_defs.h>
+#include <dhcp4/dhcp4_dhcp4o6_ipc.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcpsrv/addr_utilities.h>
@@ -164,9 +167,33 @@ Dhcpv4Exchange::initResponse() {
     if (resp_type > 0) {
         resp_.reset(new Pkt4(resp_type, getQuery()->getTransid()));
         copyDefaultFields();
+
+        if (getQuery()->isDhcp4o6()) {
+            initResponse4o6();
+        }
     }
 }
 
+void
+Dhcpv4Exchange::initResponse4o6() {
+    Pkt4o6Ptr query = boost::dynamic_pointer_cast<Pkt4o6>(getQuery());
+    if (!query) {
+        return;
+    }
+    const Pkt6Ptr& query6 = query->getPkt6();
+    Pkt6Ptr resp6(new Pkt6(DHCPV6_DHCPV4_RESPONSE, query6->getTransid()));
+    // Don't add client-id or server-id
+    // But copy relay info
+    if (!query6->relay_info_.empty()) {
+        resp6->copyRelayInfo(query6);
+    }
+    // Copy interface and remote address
+    resp6->setIface(query6->getIface());
+    resp6->setIndex(query6->getIndex());
+    resp6->setRemoteAddr(query6->getRemoteAddr());
+    resp_.reset(new Pkt4o6(resp_, resp6));
+}
+
 void
 Dhcpv4Exchange::copyDefaultFields() {
     resp_->setIface(query_->getIface());
@@ -270,6 +297,13 @@ Dhcpv4Srv::~Dhcpv4Srv() {
         LOG_ERROR(dhcp4_logger, DHCP4_SRV_D2STOP_ERROR).arg(ex.what());
     }
 
+    try {
+        Dhcp4o6Ipc::instance().close();
+    } catch(const std::exception& ex) {
+        // Highly unlikely, but lets Report it but go on
+        // LOG_ERROR(dhcp4_logger, DHCP4_SRV_DHCP4O6_ERROR).arg(ex.what());
+    }
+
     IfaceMgr::instance().closeSockets();
 }
 
@@ -282,6 +316,11 @@ Dhcpv4Srv::shutdown() {
 isc::dhcp::Subnet4Ptr
 Dhcpv4Srv::selectSubnet(const Pkt4Ptr& query) const {
 
+    // DHCPv4-over-DHCPv6 is a special (and complex) case
+    if (query->isDhcp4o6()) {
+        return (selectSubnet4o6(query));
+    }
+
     Subnet4Ptr subnet;
 
     SubnetSelector selector;
@@ -347,14 +386,106 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& query) const {
     return (subnet);
 }
 
+isc::dhcp::Subnet4Ptr
+Dhcpv4Srv::selectSubnet4o6(const Pkt4Ptr& query) const {
+
+    CfgMgr& cfgmgr = CfgMgr::instance();
+    Subnet4Ptr subnet;
+    Subnet6Ptr subnet6;
+    SubnetSelector selector;
+    selector.client_classes_ = query->classes_;
+
+    // DHCPv4 relay or option
+    selector.giaddr_ = query->getGiaddr();
+    if (!selector.giaddr_.isV4Zero()) {
+        subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet(selector);
+    }
+
+    // DHCPv6 relay
+    if (!subnet) {
+        Pkt4o6Ptr query4o6 = boost::dynamic_pointer_cast<Pkt4o6>(query);
+        if (!query4o6) {
+            isc_throw(Unexpected, "Can't get DHCP4o6 message");
+        }
+        const Pkt6Ptr& query6 = query4o6->getPkt6();
+        if (query6 && !query6->relay_info_.empty()) {
+            selector.first_relay_linkaddr_ = query6->relay_info_.back().linkaddr_;
+            selector.interface_id_ =
+                query6->getAnyRelayOption(D6O_INTERFACE_ID, Pkt6::RELAY_GET_FIRST);
+            subnet6 =
+                cfgmgr.getCurrentCfg()->getCfgSubnets6()->selectSubnet(selector);
+        }
+        if (subnet6) {
+            // Rely on matching IDs
+            const Subnet4Collection* subnets =
+                cfgmgr.getCurrentCfg()->getCfgSubnets4()->getAll();
+            for (Subnet4Collection::const_iterator it = subnets->begin();
+                 it != subnets->end(); ++it) {
+                if ((*it)->getID() == subnet6->getID()) {
+                    subnet = *it;
+                }
+            }
+        }
+    }
+
+    // Applying default DHCPv4 rules
+    if (!subnet) {
+        selector.ciaddr_ = query->getCiaddr();
+        selector.remote_address_ = query->getRemoteAddr();
+        selector.iface_name_ = query->getIface();
+
+        subnet =
+            cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet(selector);
+    }
+
+    // Let's execute all callouts registered for subnet4_select
+    // TODO
+
+    if (subnet) {
+        // Log at higher debug level that subnet has been found.
+        LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC_DATA, DHCP4_SUBNET_SELECTED)
+            .arg(query->getLabel())
+            .arg(subnet->getID());
+        // Log detailed information about the selected subnet at the
+        // lower debug level.
+        LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_DATA)
+            .arg(query->getLabel())
+            .arg(subnet->toText());
+
+    } else {
+        LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL,
+                  DHCP4_SUBNET_SELECTION_FAILED)
+            .arg(query->getLabel());
+    }
+
+    return (subnet);
+}
+
 Pkt4Ptr
 Dhcpv4Srv::receivePacket(int timeout) {
-    return (IfaceMgr::instance().receive4(timeout));
+    Pkt4Ptr pkt = IfaceMgr::instance().receive4(timeout);
+    if (!pkt) {
+        Pkt4o6Ptr& pkt4o6 = Dhcp4o6Ipc::instance().getReceived();
+        if (pkt4o6) {
+            pkt = pkt4o6;
+            pkt4o6.reset();
+        }
+    }
+    return (pkt);
 }
 
 void
 Dhcpv4Srv::sendPacket(const Pkt4Ptr& packet) {
-    IfaceMgr::instance().send(packet);
+    if (!packet->isDhcp4o6()) {
+        IfaceMgr::instance().send(packet);
+    } else {
+        Pkt4o6Ptr pkt4o6 = boost::dynamic_pointer_cast<Pkt4o6>(packet);
+        // Should not happen
+        if (!pkt4o6) {
+            isc_throw(Unexpected, "Dhcp4o6 packet cast fail");
+        }
+        Dhcp4o6Ipc::instance().send(pkt4o6->getPkt6());
+    }
 }
 
 bool
@@ -1497,7 +1628,9 @@ Dhcpv4Srv::adjustIfaceData(Dhcpv4Exchange& ex) {
     // Instead we will need to use the address assigned to the interface
     // on which the query has been received. In other cases, we will just
     // use this address as a source address for the response.
-    if (local_addr == IOAddress::IPV4_BCAST_ADDRESS()) {
+    // Do the same for DHCPv4-over-DHCPv6 exchanges.
+    if ((local_addr == IOAddress::IPV4_BCAST_ADDRESS()) ||
+        (query->isDhcp4o6())) {
         SocketInfo sock_info = IfaceMgr::instance().getSocket(*query);
         local_addr = sock_info.addr_;
     }
@@ -1529,6 +1662,12 @@ Dhcpv4Srv::adjustRemoteAddr(Dhcpv4Exchange& ex) {
     Pkt4Ptr query = ex.getQuery();
     Pkt4Ptr response = ex.getResponse();
 
+    // DHCPv4-over-DHCPv6 is simple
+    if (query->isDhcp4o6()) {
+        response->setRemoteAddr(query->getRemoteAddr());
+        return;
+    }
+
     // The DHCPINFORM is slightly different than other messages in a sense
     // that the server should always unicast the response to the ciaddr.
     // It appears however that some clients don't set the ciaddr. We still
@@ -1912,6 +2051,12 @@ Dhcpv4Srv::acceptDirectRequest(const Pkt4Ptr& pkt) const {
     if (pkt->isRelayed()) {
         return (true);
     }
+
+    // Accept all DHCPv4-over-DHCPv6 messages.
+    if (pkt->isDhcp4o6()) {
+        return (true);
+    }
+
     // The source address must not be zero for the DHCPINFORM message from
     // the directly connected client because the server will not know where
     // to respond if the ciaddr was not present.
index e96c0bd61afe5dade62d90f7293610d7c49ff850..ec2b9374f2b3f7856eb5793e5a4db55273da5c7b 100644 (file)
@@ -17,6 +17,8 @@
 
 #include <dhcp/dhcp4.h>
 #include <dhcp/pkt4.h>
+#include <dhcp/pkt4o6.h>
+#include <dhcp/pkt6.h>
 #include <dhcp/option.h>
 #include <dhcp/option_string.h>
 #include <dhcp/option4_client_fqdn.h>
@@ -95,6 +97,11 @@ public:
     /// response is not initialized.
     void initResponse();
 
+    /// @brief Initializes the DHCPv6 part of the response message
+    ///
+    /// Called by initResponse() when the query is a DHCP4o6 message
+    void initResponse4o6();
+
     /// @brief Returns the pointer to the query from the client.
     Pkt4Ptr getQuery() const {
         return (query_);
@@ -690,6 +697,12 @@ protected:
     /// @return selected subnet (or NULL if no suitable subnet was found)
     isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& query) const;
 
+    /// @brief Selects a subnet for a given client's DHCP4o6 packet.
+    ///
+    /// @param query client's message
+    /// @return selected subnet (or NULL if no suitable subnet was found)
+    isc::dhcp::Subnet4Ptr selectSubnet4o6(const Pkt4Ptr& query) const;
+
     /// indicates if shutdown is in progress. Setting it to true will
     /// initiate server shutdown procedure.
     volatile bool shutdown_;
index 36e06cb749180491555761bd1420b01968a4bba8..01be83c4ccd29355602d86f4533c3d8c86e11966 100644 (file)
@@ -238,6 +238,9 @@ protected:
         SubnetID subnet_id =
             static_cast<SubnetID>(uint32_values_->getOptionalParam("id", 0));
 
+        Subnet4Ptr subnet4(new Subnet4(addr, len, t1, t2, valid, subnet_id));
+       subnet_id = subnet4->getID();
+
         stringstream s;
         s << addr << "/" << static_cast<int>(len) << " with params: ";
         // t1 and t2 are optional may be not specified.
@@ -247,11 +250,11 @@ protected:
         if (!t2.unspecified()) {
             s << "t2=" << t2 << ", ";
         }
-        s <<"valid-lifetime=" << valid;
+        s << "valid-lifetime=" << valid;
+       s << ", id=" << subnet_id;
 
         LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(s.str());
 
-        Subnet4Ptr subnet4(new Subnet4(addr, len, t1, t2, valid, subnet_id));
         subnet_ = subnet4;
 
         // match-client-id
@@ -314,7 +317,7 @@ protected:
     }
 };
 
-/// @brief this class parses list of DHCP4 subnets
+/// @brief this class parses list of IPv4 subnets
 ///
 /// This is a wrapper parser that handles the whole list of Subnet4
 /// definitions. It iterates over all entries and creates Subnet4ConfigParser
@@ -356,6 +359,225 @@ public:
     }
 };
 
+/// @brief This class parses a single IPv6 subnet.
+///
+/// This is the IPv6 derivation of the SubnetConfigParser class and it parses
+/// the whole subnet definition. It creates parsersfor received configuration
+/// parameters as needed.
+class Subnet6ConfigParser : public SubnetConfigParser {
+public:
+
+    /// @brief Constructor
+    ///
+    /// @param ignored first parameter
+    /// stores global scope parameters, options, option definitions.
+    Subnet6ConfigParser(const std::string&)
+        :SubnetConfigParser("", globalContext(), IOAddress("::")) {
+    }
+
+    /// @brief Parses a single IPv6 subnet configuration and adds to the
+    /// Configuration Manager.
+    ///
+    /// @param subnet A new subnet being configured.
+    void build(ConstElementPtr subnet) {
+        SubnetConfigParser::build(subnet);
+
+        if (subnet_) {
+            Subnet6Ptr sub6ptr = boost::dynamic_pointer_cast<Subnet6>(subnet_);
+            if (!sub6ptr) {
+                // If we hit this, it is a programming error.
+                isc_throw(Unexpected,
+                          "Invalid cast in Subnet6ConfigParser::commit");
+            }
+
+            // Set relay information if it was provided
+            if (relay_info_) {
+                sub6ptr->setRelayInfo(*relay_info_);
+            }
+
+            // Adding a subnet to the Configuration Manager may fail if the
+            // subnet id is invalid (duplicate). Thus, we catch exceptions
+            // here to append a position in the configuration string.
+            try {
+                CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(sub6ptr);
+            } catch (const std::exception& ex) {
+                isc_throw(DhcpConfigError, ex.what() << " ("
+                          << subnet->getPosition() << ")");
+            }
+        }
+    }
+
+    /// @brief Commits subnet configuration.
+    ///
+    /// This function is currently no-op because subnet should already
+    /// be added into the Config Manager in the build().
+    void commit() { }
+
+protected:
+
+    /// @brief creates parsers for entries in subnet definition
+    ///
+    /// @param config_id name of the entry
+    ///
+    /// @return parser object for specified entry name. Note the caller is
+    /// responsible for deleting the parser created.
+    /// @throw isc::dhcp::DhcpConfigError if trying to create a parser
+    /// for unknown config element
+    DhcpConfigParser* createSubnetConfigParser(const std::string& config_id) {
+        DhcpConfigParser* parser = NULL;
+        if (config_id.compare("id") == 0) {
+            parser = new Uint32Parser(config_id, uint32_values_);
+        } else if ((config_id.compare("subnet") == 0) ||
+                   (config_id.compare("interface") == 0) ||
+                   (config_id.compare("client-class") == 0) ||
+                   (config_id.compare("interface-id") == 0)) {
+            parser = new StringParser(config_id, string_values_);
+        } else if (config_id.compare("relay") == 0) {
+            parser = new RelayInfoParser(config_id, relay_info_, Option::V6);
+        } else {
+            isc_throw(NotImplemented, "unsupported parameter: " << config_id);
+        }
+
+        return (parser);
+    }
+
+    /// @brief Issues a DHCP server specific warning regarding duplicate subnet
+    /// options.
+    ///
+    /// @param code is the numeric option code of the duplicate option
+    /// @param addr is the subnet address
+    /// @todo A means to know the correct logger and perhaps a common
+    /// message would allow this message to be emitted by the base class.
+    /// Required to make Subnet6ConfigParser not abstract.
+    virtual void duplicate_option_warning(uint32_t code,
+                                         isc::asiolink::IOAddress& addr) {
+        LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
+            .arg(code).arg(addr.toText());
+    }
+
+    /// @brief Instantiates the IPv6 Subnet based on a given IPv6 address
+    /// and prefix length.
+    ///
+    /// @param addr is IPv6 prefix of the subnet.
+    /// @param len is the prefix length
+    void initSubnet(isc::asiolink::IOAddress addr, uint8_t len) {
+        // Subnet ID is not optional because without IPv6 subnets
+        // can't be used for DHCPv4-over-DHCPv6 subnet selection.
+        SubnetID subnet_id =
+            static_cast<SubnetID>(uint32_values_->getParam("id"));
+
+        // Get interface-id option content. For now we support string
+        // representation only
+        std::string ifaceid;
+        try {
+            ifaceid = string_values_->getParam("interface-id");
+        } catch (const DhcpConfigError &) {
+            // interface-id is not mandatory
+        }
+
+        // Specifying both interface for locally reachable subnets and
+        // interface id for relays is mutually exclusive. Need to test for
+        // this condition.
+        if (!ifaceid.empty()) {
+            std::string iface;
+            try {
+                iface = string_values_->getParam("interface");
+            } catch (const DhcpConfigError &) {
+                // iface not mandatory
+            }
+
+            if (!iface.empty()) {
+                isc_throw(isc::dhcp::DhcpConfigError,
+                      "parser error: interface (defined for locally reachable "
+                      "subnets) and interface-id (defined for subnets reachable"
+                      " via relays) cannot be defined at the same time for "
+                      "subnet " << addr << "/" << (int)len);
+            }
+        }
+
+        // Create a new subnet.
+        Triplet<uint32_t> t;
+        Subnet6* subnet6 = new Subnet6(addr, len, t, t, t, t, subnet_id);
+        subnet_id = subnet6->getID();
+
+        std::ostringstream output;
+        output << addr << "/" << static_cast<int>(len) << " id=" << subnet_id;
+
+        LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(output.str());
+
+        // Configure interface-id for remote interfaces, if defined
+        if (!ifaceid.empty()) {
+            OptionBuffer tmp(ifaceid.begin(), ifaceid.end());
+            OptionPtr opt(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+            subnet6->setInterfaceId(opt);
+        }
+
+        // Try setting up client class (if specified)
+        try {
+            string client_class = string_values_->getParam("client-class");
+            subnet6->allowClientClass(client_class);
+        } catch (const DhcpConfigError&) {
+            // That's ok if it fails. client-class is optional.
+        }
+
+        subnet_.reset(subnet6);
+    }
+
+};
+
+
+/// @brief this class parses a list of IPv6 subnets
+///
+/// This is a wrapper parser that handles the whole list of Subnet6
+/// definitions. It iterates over all entries and creates Subnet6ConfigParser
+/// for each entry.
+class Subnets6ListConfigParser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor
+    ///
+    /// @param dummy first argument, always ignored. All parsers accept a
+    /// string parameter "name" as their first argument.
+    Subnets6ListConfigParser(const std::string&) {
+    }
+
+    /// @brief parses contents of the list
+    ///
+    /// Iterates over all entries on the list and creates a Subnet6ConfigParser
+    /// for each entry.
+    ///
+    /// @param subnets_list pointer to a list of IPv6 subnets
+    void build(ConstElementPtr subnets_list) {
+        BOOST_FOREACH(ConstElementPtr subnet, subnets_list->listValue()) {
+            ParserPtr parser(new Subnet6ConfigParser("subnet"));
+            parser->build(subnet);
+            subnets_.push_back(parser);
+        }
+
+    }
+
+    /// @brief commits subnets definitions.
+    ///
+    /// Iterates over all Subnet6 parsers. Each parser contains definitions of
+    /// a single subnet and its parameters and commits each subnet separately.
+    void commit() {
+        BOOST_FOREACH(ParserPtr subnet, subnets_) {
+            subnet->commit();
+        }
+
+    }
+
+    /// @brief Returns Subnet6ListConfigParser object
+    /// @param param_name name of the parameter
+    /// @return Subnets6ListConfigParser object
+    static DhcpConfigParser* factory(const std::string& param_name) {
+        return (new Subnets6ListConfigParser(param_name));
+    }
+
+    /// @brief collection of subnet parsers.
+    ParserCollection subnets_;
+};
+
 } // anonymous namespace
 
 namespace isc {
@@ -377,13 +599,16 @@ namespace dhcp {
     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_);
     } else if (config_id.compare("interfaces-config") == 0) {
         parser = new IfacesConfigParser4();
     } else if (config_id.compare("subnet4") == 0) {
         parser = new Subnets4ListConfigParser(config_id);
+    } else if (config_id.compare("subnet6") == 0) {
+        parser = new Subnets6ListConfigParser(config_id);
     } else if (config_id.compare("option-data") == 0) {
         parser = new OptionDataListParser(config_id, CfgOptionPtr(), AF_INET);
     } else if (config_id.compare("option-def") == 0) {
@@ -419,6 +644,7 @@ namespace dhcp {
 ///
 /// - 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.
@@ -441,6 +667,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
@@ -466,11 +701,12 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
     // depend on the global values. Also, option values configuration
     // must be performed after the option definitions configurations.
     // Thus we group parsers and will fire them in the right order:
-    // all parsers other than: lease-database, subnet4 and option-data parser,
-    // then: option-data parser, subnet4 parser, lease-database parser.
+    // all parsers other than: lease-database, subnet and option-data parser,
+    // then: option-data parser, subnet parser, lease-database parser.
     // Please do not change this order!
     ParserCollection independent_parsers;
-    ParserPtr subnet_parser;
+    ParserPtr subnet4_parser;
+    ParserPtr subnet6_parser;
     ParserPtr option_parser;
     ParserPtr iface_parser;
     ParserPtr leases_parser;
@@ -508,7 +744,9 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
             LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PARSER_CREATED)
                       .arg(config_pair.first);
             if (config_pair.first == "subnet4") {
-                subnet_parser = parser;
+                subnet4_parser = parser;
+            } else if (config_pair.first == "subnet6") {
+                subnet6_parser = parser;
             } else if (config_pair.first == "lease-database") {
                 leases_parser = parser;
             } else if (config_pair.first == "option-data") {
@@ -530,7 +768,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
                 independent_parsers.push_back(parser);
                 parser->build(config_pair.second);
                 // The commit operation here may modify the global storage
-                // but we need it so as the subnet6 parser can access the
+                // but we need it so as subnet parsers can access the
                 // parsed data.
                 parser->commit();
             }
@@ -545,12 +783,18 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
             option_parser->commit();
         }
 
-        // The subnet parser is the next one to be run.
-        std::map<std::string, ConstElementPtr>::const_iterator subnet_config =
+        // Subnet parsers are the next to be run.
+        std::map<std::string, ConstElementPtr>::const_iterator subnet4_config =
             values_map.find("subnet4");
-        if (subnet_config != values_map.end()) {
+        if (subnet4_config != values_map.end()) {
             config_pair.first = "subnet4";
-            subnet_parser->build(subnet_config->second);
+            subnet4_parser->build(subnet4_config->second);
+        }
+        std::map<std::string, ConstElementPtr>::const_iterator subnet6_config =
+            values_map.find("subnet6");
+        if (subnet6_config != values_map.end()) {
+            config_pair.first = "subnet6";
+            subnet6_parser->build(subnet6_config->second);
         }
 
         // Get command socket configuration from the config file.
index 7da28a597329cead9879353ea7f686eb0ac5e6c1..f0a2301639d05e2b67a095b41f3acbd7f4baaaf2 100644 (file)
@@ -64,6 +64,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
 
index 7a1e84b230ea267da73dcb750e2c69a22c5567b9..b26b000eeedc2d15f5c1c1dd39b1f4512360f8ce 100644 (file)
@@ -17,6 +17,7 @@
 #include <config/command_mgr.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>
@@ -143,6 +144,16 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
         return (isc::config::createAnswer(1, err.str()));
     }
 
+    // Setup DHCPv4-over-DHCPv6 IPC
+    try {
+        Dhcp4o6Ipc::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
diff --git a/src/bin/dhcp6/dhcp6_dhcp4o6_ipc.cc b/src/bin/dhcp6/dhcp6_dhcp4o6_ipc.cc
new file mode 100644 (file)
index 0000000..d3b7586
--- /dev/null
@@ -0,0 +1,71 @@
+// 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 {
+
+Dhcp4o6Ipc::Dhcp4o6Ipc() : Dhcp4o6IpcBase() {}
+
+Dhcp4o6Ipc& Dhcp4o6Ipc::instance() {
+    static Dhcp4o6Ipc dhcp4o6_ipc;
+    return (dhcp4o6_ipc);
+}
+
+void Dhcp4o6Ipc::open() {
+    uint32_t port = CfgMgr::instance().getStagingCfg()->getDhcp4o6Port();
+    if (port == 0) {
+        Dhcp4o6IpcBase::close();
+        return;
+    }
+    if (port > 65534) {
+        isc_throw(OutOfRange, "DHCP4o6 port " << port);
+    }
+
+    int old_fd = socket_fd_;
+    socket_fd_ = Dhcp4o6IpcBase::open(static_cast<uint16_t>(port), 6);
+    if ((old_fd == -1) && (socket_fd_ != old_fd)) {
+        IfaceMgr::instance().addExternalSocket(socket_fd_, Dhcp4o6Ipc::handler);
+    }
+}
+
+void Dhcp4o6Ipc::handler() {
+    Dhcp4o6Ipc& ipc = Dhcp4o6Ipc::instance();
+    Pkt6Ptr pkt = ipc.receive();
+    if (!pkt) {
+        return;
+    }
+    isc::util::OutputBuffer& buf = pkt->getBuffer();
+    pkt->repack();
+    uint8_t msg_type = buf[0];
+    if ((msg_type == DHCPV6_RELAY_FORW) || (msg_type == DHCPV6_RELAY_REPL)) {
+        pkt->setRemotePort(DHCP6_SERVER_PORT);
+    } else {
+        pkt->setRemotePort(DHCP6_CLIENT_PORT);
+    }
+    IfaceMgr::instance().send(pkt);
+    // processStatsSent(pkt);
+}
+
+};  // namespace dhcp
+
+};  // namespace isc
diff --git a/src/bin/dhcp6/dhcp6_dhcp4o6_ipc.h b/src/bin/dhcp6/dhcp6_dhcp4o6_ipc.h
new file mode 100644 (file)
index 0000000..901a6f2
--- /dev/null
@@ -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 Dhcp4o6Ipc : public Dhcp4o6IpcBase {
+protected:
+    /// @brief Constructor
+    ///
+    /// Default constructor
+    Dhcp4o6Ipc();
+
+    /// @brief Destructor.
+    virtual ~Dhcp4o6Ipc() { }
+
+public:
+    /// @brief Returns pointer to the sole instance of Dhcp4o6Ipc
+    ///
+    /// Dhcp4o6Ipc is a singleton class
+    ///
+    /// @return the only existing instance of DHCP4o6 IPC
+    static Dhcp4o6Ipc& 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
index 639be48038b47e1a4351df31fd525cfc1b45d269..6ded7b08878d7694bdf3f752501fa23f1e880141 100644 (file)
@@ -32,6 +32,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>
@@ -236,6 +237,13 @@ Dhcpv6Srv::~Dhcpv6Srv() {
         LOG_ERROR(dhcp6_logger, DHCP6_SRV_D2STOP_ERROR).arg(ex.what());
     }
 
+    try {
+        Dhcp4o6Ipc::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();
@@ -571,6 +579,10 @@ bool Dhcpv6Srv::run() {
                 rsp = processInfRequest(query);
                 break;
 
+            case DHCPV6_DHCPV4_QUERY:
+                rsp = processDhcp4Query(query);
+                break;
+
             default:
                 // We received a packet type that we do not recognize.
                 LOG_DEBUG(bad_packet6_logger, DBG_DHCP6_BASIC, DHCP6_UNKNOWN_MSG_RECEIVED)
@@ -2619,6 +2631,26 @@ Dhcpv6Srv::processInfRequest(const Pkt6Ptr& inf_request) {
     return (reply);
 }
 
+Pkt6Ptr
+Dhcpv6Srv::processDhcp4Query(const Pkt6Ptr& dhcp4_query) {
+
+    sanityCheck(dhcp4_query, OPTIONAL, OPTIONAL);
+
+    // flags are in transid
+    // uint32_t flags = dhcp4_query->getTransid();
+    // do nothing with DHCPV4_QUERY_FLAGS_UNICAST
+
+    // Get the DHCPv4 message option
+    OptionPtr dhcp4_msg = dhcp4_query->getOption(D6O_DHCPV4_MSG);
+    if (dhcp4_msg) {
+        // Forward the whole message to the DHCPv4 server via IPC
+        Dhcp4o6Ipc::instance().send(dhcp4_query);
+    }
+
+    // Our job is finished
+    return (Pkt6Ptr());
+}
+
 size_t
 Dhcpv6Srv::unpackOptions(const OptionBuffer& buf,
                          const std::string& option_space,
index 2eb0044343e114654214cec1066b5816ec1fba5a..606dd3ea44a456ddb9108f6fd5b1663505abe9dd 100644 (file)
@@ -274,6 +274,12 @@ protected:
     /// @return Reply message to be sent to the client.
     Pkt6Ptr processInfRequest(const Pkt6Ptr& inf_request);
 
+    /// @brief Processes incoming DHCPv4-query message.
+    ///
+    /// @param dhcp4_query message received from client
+    /// @return Reply (empty) message to (not) be sent to the client.
+    Pkt6Ptr processDhcp4Query(const Pkt6Ptr& dhcp4_query);
+
     /// @brief Selects a subnet for a given client's packet.
     ///
     /// @param question client's message
index 89a90508bd32825229bfb08fe96b1492e9c92e12..a3a5daaf912302949d10fcc96e3af79fe9ff2a57 100644 (file)
@@ -670,7 +670,8 @@ namespace dhcp {
         (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) {
@@ -711,6 +712,7 @@ namespace dhcp {
 /// Currently this method sets the following global parameters:
 ///
 /// - decline-probation-period
+/// - dhcp4o6-port
 void setGlobalParameters6() {
 
     // Set the probation period for decline handling.
@@ -722,6 +724,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
index 549be78da8b4dd096e9de192036a5379af280006..458c7895e45d4dd8cee9fa9f4e25391abdc8bb17 100644 (file)
@@ -366,6 +366,14 @@ public:
     /// (true) or non-relayed (false).
     bool isRelayed() const;
 
+    /// @brief Checks if a DHCPv4 message has beeb transported over DHCPv6
+    ///
+    /// @return Boolean value which indicates whether the message is
+    /// transported over DHCPv6 (true) or native DHCPv4 (false)
+    virtual bool isDhcp4o6() const {
+        return (false);
+    }
+
 private:
 
     /// @brief Generic method that validates and sets HW address.
index f328e82f7da105966eea640e581334484accb0f9..7fb087a6e31bebb630e98cc7354e62cf1adaa26c 100644 (file)
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <config.h>
-#include <asiolink/io_address.h>
-#include <dhcp/dhcp4.h>
-#include <dhcp/libdhcp++.h>
-#include <dhcp/option_int.h>
+
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
 #include <dhcp/pkt4o6.h>
 #include <exceptions/exceptions.h>
+#include <util/buffer.h>
 
-#include <algorithm>
-#include <iostream>
-#include <sstream>
-
-using namespace std;
-using namespace isc::dhcp;
 using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace std;
 
 namespace {
 
@@ -37,14 +34,29 @@ const IOAddress DEFAULT_ADDRESS("0.0.0.0");
 namespace isc {
 namespace dhcp {
 
-Pkt4o6::Pkt4o6(const uint8_t* data, size_t len, const Pkt6Ptr& pkt6)
-    :Pkt4(data, len), pkt6_(pkt6)
+Pkt4o6::Pkt4o6(const OptionBuffer& pkt4, const Pkt6Ptr& pkt6)
+    :Pkt4(&pkt4[0], pkt4.size()), pkt6_(pkt6)
 {
+    static_cast<void>(pkt6->delOption(D6O_DHCPV4_MSG));
     setIface(pkt6->getIface());
     setIndex(pkt6->getIndex());
     setRemoteAddr(pkt6->getRemoteAddr());
 }
 
+Pkt4o6::Pkt4o6(const Pkt4Ptr& pkt4, const Pkt6Ptr& pkt6)
+    :Pkt4(*pkt4), pkt6_(pkt6) {
+}
+
+void Pkt4o6::pack() {
+    Pkt4::pack();
+    OutputBuffer& buf = getBuffer();
+    const uint8_t* ptr = static_cast<const uint8_t*>(buf.getData());
+    OptionBuffer msg(ptr, ptr + buf.getLength());
+    OptionPtr dhcp4_msg(new Option(Option::V6, D6O_DHCPV4_MSG, msg));
+    pkt6_->addOption(dhcp4_msg);
+    pkt6_->pack();
+}
+
 } // end of namespace isc::dhcp
 
 } // end of namespace isc
index 7e6f1877d2066f23b0791c5803c8790d6ef2b4ef..f90ae4eb61e3ed252c251a075b4922e941377f25 100644 (file)
@@ -34,13 +34,34 @@ public:
 
     /// @brief Constructor, used in message reception.
     ///
-    /// @param data pointer to received data
-    /// @param len size of buffer to be allocated for this packet
-    /// @param pkt6 encapsulating DHCPv6 message.
-    Pkt4o6(const uint8_t* data, size_t len, const Pkt6Ptr& pkt6);
+    /// @param pkt4 DHCPv4 message
+    /// @param pkt6 encapsulating unpacked DHCPv6 message
+    /// the DHCPv4 message option will be removed
+    Pkt4o6(const OptionBuffer& pkt4, const Pkt6Ptr& pkt6);
+
+    /// @brief Constructor, used in replying to a message
+    ///
+    /// @param pkt4 DHCPv4 message
+    /// @param pkt6 DHCPv6 message
+    Pkt4o6(const Pkt4Ptr& pkt4, const Pkt6Ptr& pkt6);
 
     /// @brief Returns encapsulating DHCPv6 message
-    const Pkt6Ptr& getPkt6() { return (pkt6_); }
+    const Pkt6Ptr& getPkt6() const { return (pkt6_); }
+
+    /// @brief Prepares on-wire format of DHCPv4-over-DHCPv6 packet.
+    ///
+    /// Calls pack() on both DHCPv4 and DHCPv6 parts
+    /// Inserts the DHCPv4-message option
+    /// @ref pkt4::pack and @ref pkt6::pack
+    virtual void pack();
+
+    /// @brief Checks if a DHCPv4 message has beeb transported over DHCPv6
+    ///
+    /// @return Boolean value which indicates whether the message is
+    /// transported over DHCPv6 (true) or native DHCPv4 (false)
+    virtual bool isDhcp4o6() const {
+        return (true);
+    }
 
 protected:
     /// Encapsulating DHCPv6 message
index 861ae4670453f40a82b80287f7c746979405c246..6554bc314cb5df6525c89b5177b562150aa54cc6 100644 (file)
@@ -207,7 +207,7 @@ private:
         // And the actual object type is the one that we expect.
         // Note that for many options there are dedicated classes
         // derived from Option class to represent them.
-       const Option* optptr = option.get();
+        const Option* optptr = option.get();
         EXPECT_TRUE(typeid(*optptr) == expected_type)
             << "Invalid class returned for option code " << code;
     }
index e5160a5ef734013627d45a2f5ff14ef3a020b23f..9b60f489185405047bbefa8417fee6a3ad5d1ea5 100644 (file)
@@ -92,6 +92,7 @@ libkea_dhcpsrv_la_SOURCES += csv_lease_file6.cc csv_lease_file6.h
 libkea_dhcpsrv_la_SOURCES += d2_client_cfg.cc d2_client_cfg.h
 libkea_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.h
 libkea_dhcpsrv_la_SOURCES += daemon.cc daemon.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
diff --git a/src/lib/dhcpsrv/dhcp4o6_ipc.cc b/src/lib/dhcpsrv/dhcp4o6_ipc.cc
new file mode 100644 (file)
index 0000000..9fcd124
--- /dev/null
@@ -0,0 +1,206 @@
+// 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/dhcp4o6.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/dhcp4o6_ipc.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(uint16_t port, int side) {
+    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.");
+    }
+
+    // Set reuse address
+    int flag = 1;
+    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+                   (char *)&flag, sizeof(flag)) < 0) {
+        ::close(sock);
+        isc_throw(Unexpected, "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.");
+    }
+
+    // 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 (side == 6) {
+        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(Unexpected, "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 (side == 6) {
+        remote6.sin6_port = htons(port + 1);
+    } else {
+        remote6.sin6_port = htons(port);
+    }
+    if (connect(sock, (struct sockaddr *)&remote6, sizeof(remote6)) < 0) {
+        ::close(sock);
+        isc_throw(Unexpected, "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.");
+        }
+        if (sock != socket_fd_) {
+            ::close(sock);
+            sock = socket_fd_;
+        }
+    }
+
+    // Success
+    port_ = port;
+    socket_fd_ = sock;
+    return (socket_fd_);
+}
+
+void Dhcp4o6IpcBase::close() {
+    static_cast<void>(open(0, 0));
+}
+
+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.");
+    }
+    if (cc < DHCP4O6_DHCP6_OFFSET + Pkt6::DHCPV6_PKT_HDR_LEN) {
+        // Ignore too short messages
+        return (Pkt6Ptr());
+    }
+    char name[DHCP4O6_IFNAME_SIZE + 1];
+    memcpy(name, buf, DHCP4O6_IFNAME_SIZE);
+    IfacePtr iface = IfaceMgr::instance().getIface(string(name));
+    if (!iface) {
+        // Can't get the interface: ignore
+        return (Pkt6Ptr());
+    }
+    uint8_t raddr[DHCP4O6_RADDR_SIZE];
+    memcpy(raddr, buf + DHCP4O6_RADDR_OFFSET, DHCP4O6_RADDR_SIZE);
+    IOAddress from = IOAddress::fromBytes(AF_INET6, raddr);
+    Pkt6Ptr pkt;
+    pkt = Pkt6Ptr(new Pkt6(buf + DHCP4O6_DHCP6_OFFSET,
+                           cc - DHCP4O6_DHCP6_OFFSET));
+    pkt->updateTimestamp();
+    pkt->setRemoteAddr(from);
+    pkt->setIface(iface->getName());
+    pkt->setIndex(iface->getIndex());
+    return (pkt);
+}
+
+void Dhcp4o6IpcBase::send(Pkt6Ptr pkt) {
+    // No packet: nothing to send
+    if (!pkt) {
+        return;
+    }
+
+    // Disabled: nowhere to send
+    if (socket_fd_ == -1) {
+        return;
+    }
+
+    // Get interface name
+    string name = pkt->getIface();
+    if (name.empty() || name.size() > DHCP4O6_IFNAME_SIZE) {
+        // Bad interface name: ignore
+        return;
+    }
+    name.resize(DHCP4O6_IFNAME_SIZE);
+
+    // Get remote address
+    IOAddress from = pkt->getRemoteAddr();
+    vector<uint8_t> raddr = from.toBytes();
+    if (raddr.size() != DHCP4O6_RADDR_SIZE) {
+        // Bad remote address: ignore
+        return;
+    }
+
+    // Get packet content
+    OutputBuffer& pbuf = pkt->getBuffer();
+    if (!pbuf.getLength()) {
+        // Empty buffer: content is not (yet) here, get it
+        pkt->repack();
+    }
+
+    // Fill buffer
+    vector<uint8_t> buf(DHCP4O6_DHCP6_OFFSET + pbuf.getLength());
+    memcpy(&buf[0], name.c_str(), DHCP4O6_IFNAME_SIZE);
+    memcpy(&buf[0] + DHCP4O6_RADDR_OFFSET, &raddr[0], DHCP4O6_RADDR_SIZE);
+    memcpy(&buf[0] + DHCP4O6_DHCP6_OFFSET, pbuf.getData(), pbuf.getLength());
+
+    // Send
+    static_cast<void>(::send(socket_fd_, &buf[0], buf.size(), 0));
+    return;
+}
+
+};  // namespace dhcp
+
+};  // namespace isc
diff --git a/src/lib/dhcpsrv/dhcp4o6_ipc.h b/src/lib/dhcpsrv/dhcp4o6_ipc.h
new file mode 100644 (file)
index 0000000..36b05ba
--- /dev/null
@@ -0,0 +1,81 @@
+// 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 <dhcp/pkt6.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief 
+///
+class Dhcp4o6IpcBase : public boost::noncopyable {
+protected:
+    /// @brief Constructor
+    ///
+    /// Default constructor
+    Dhcp4o6IpcBase();
+
+    /// @brief Destructor.
+    virtual ~Dhcp4o6IpcBase();
+
+    /// @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)
+    ///
+    /// @return new socket descriptor
+    int open(uint16_t port, int side);
+
+public:
+    /// @brief Open communication socket (for derived classes)
+    virtual void open() = 0;
+
+    /// @brief Close communication socket
+    void close();
+
+    /// @brief Receive IPC message
+    ///
+    /// @return a pointer to a DHCPv6 message with interface and remote
+    /// address set from the IPC message
+    Pkt6Ptr receive();
+
+    /// @brief Send IPC message
+    ///
+    /// @param a pointer to a DHCPv6 message with interface and remote
+    /// address set for the IPC message
+    void send(Pkt6Ptr pkt);
+
+protected:
+    /// @brief Port number
+    uint16_t port_;
+
+    /// @brief Socket descriptor
+    int socket_fd_;
+};
+
+} // namespace isc
+} // namespace dhcp
+
+#endif
index bdf4ea3763081a11654308b5097d05b556a2b840..ca577827f40185498da165391ab74ccc8e746400 100644 (file)
@@ -31,7 +31,7 @@ SrvConfig::SrvConfig()
       cfg_option_def_(new CfgOptionDef()), cfg_option_(new CfgOption()),
       cfg_subnets4_(new CfgSubnets4()), cfg_subnets6_(new CfgSubnets6()),
       cfg_hosts_(new CfgHosts()), cfg_rsoo_(new CfgRSOO()),
-      decline_timer_(0) {
+      decline_timer_(0), dhcp4o6_port_(0) {
 }
 
 SrvConfig::SrvConfig(const uint32_t sequence)
@@ -39,7 +39,7 @@ SrvConfig::SrvConfig(const uint32_t sequence)
       cfg_option_def_(new CfgOptionDef()), cfg_option_(new CfgOption()),
       cfg_subnets4_(new CfgSubnets4()), cfg_subnets6_(new CfgSubnets6()),
       cfg_hosts_(new CfgHosts()), cfg_rsoo_(new CfgRSOO()),
-      decline_timer_(0) {
+      decline_timer_(0), dhcp4o6_port_(0) {
 }
 
 std::string
index b1b9dbe88e9d6351e41492caeee1c5bb4d896470..ed22411641ee4ddbdf3d0fb9e74880a471a07d81 100644 (file)
@@ -394,6 +394,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.
@@ -449,6 +467,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.