Browse Source

[master] Merge trac4110a (DHCP4o6 v4 server part)

Francis Dupont 8 years ago
parent
commit
7d36aebe14

+ 41 - 0
src/bin/dhcp4/dhcp4_messages.mes

@@ -198,6 +198,41 @@ detected as a duplicate (i.e. another device in the network is using this addres
 However, the server does not have a record for this address. This may indicate
 a client's error or a server's purged database.
 
+% DHCP4_DHCP4O6_BAD_PACKET received malformed DHCPv4o6 packet: %1
+A malformed DHCPv4o6 packet was received.
+
+% DHCP6_DHCP4O6_PACKET_RECEIVED received DHCPv4o6 packet from DHCPv6 server (type %1) for %2 on interface %3
+This debug message is printed when the server is receiving a DHCPv4o6
+from the DHCPv6 server over inter-process communication.
+
+% DHCP4_DHCP4O6_PACKET_SEND %1: trying to send packet %2 (type %3) to %4 on interface %5 encapsulating %6: %7 (type %8)
+The arguments specify the client identification information (HW address
+and client identifier), DHCPv6 message name and type, source IPv6
+address and interface name, DHCPv4 client identification, message
+name and type.
+
+% DHCP4_DHCP4O6_PACKET_SEND_FAIL %1: failed to send DHCPv4o6 packet: %2
+This error is output if the IPv4 DHCP server fails to send an
+DHCPv4o6 message to the IPv6 DHCP server. The reason for the
+error is included in the message.
+
+% DHCP4_DHCP4O6_RECEIVE_FAIL failed to receive DHCPv4o6: %1
+This debug message indicates the inter-process communication with the
+DHCPv6 server failed. The reason for the error is included in
+the message.
+
+% DHCP4_DHCP4O6_RECEIVING receiving DHCPv4o6 packet from DHCPv6 server
+This debug message is printed when the server is receiving a DHCPv4o6
+from the DHCPv6 server over inter-process communication socket.
+
+% DHCP4_DHCP4O6_RESPONSE_DATA %1: responding with packet %2 (type %3), packet details: %4
+A debug message including the detailed data about the packet being
+sent to the DHCPv6 server to be forwarded to the client. The first
+argument contains the client and the transaction identification
+information. The second and third argument contains the packet name
+and type respectively. The fourth argument contains detailed packet
+information.
+
 % DHCP4_DISCOVER_CLASS_PROCESSING_FAILED %1: client class specific processing failed for DHCPDISCOVER
 This debug message means that the server processing that is unique for each
 client class has reported a failure. The response packet will not be sent.
@@ -641,6 +676,12 @@ stopping IO between the DHCPv4 server and the DHCP_DDNS server.  This is
 probably due to a programmatic error is not likely to impact either server
 upon restart.  The reason for the failure is given within the message.
 
+% DHCP4_SRV_DHCP4O6_ERROR error stopping IO with DHCPv4o6 during shutdown: %1
+This error message indicates that during shutdown, an error occurred while
+stopping IO between the DHCPv4 server and the DHCPv6o6 server.  This is
+probably due to a programmatic error is not likely to impact either server
+upon restart.  The reason for the failure is given within the message.
+
 % DHCP4_STARTED Kea DHCPv4 server version %1 started
 This informational message indicates that the DHCPv4 server has
 processed all configuration information and is ready to process

+ 192 - 14
src/bin/dhcp4/dhcp4_srv.cc

@@ -16,7 +16,10 @@
 #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/dhcp4to6_ipc.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcpsrv/addr_utilities.h>
@@ -173,7 +176,31 @@ Dhcpv4Exchange::initResponse() {
         resp_.reset(new Pkt4(resp_type, getQuery()->getTransid()));
         copyDefaultFields();
         copyDefaultOptions();
+
+        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
@@ -311,8 +338,7 @@ const std::string Dhcpv4Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
 Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const bool use_bcast,
                      const bool direct_response_desired)
     : shutdown_(true), alloc_engine_(), port_(port),
-      use_bcast_(use_bcast), hook_index_pkt4_receive_(-1),
-      hook_index_subnet4_select_(-1), hook_index_pkt4_send_(-1) {
+      use_bcast_(use_bcast) {
 
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
     try {
@@ -336,11 +362,6 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const bool use_bcast,
         alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 0,
                                             false /* false = IPv4 */));
 
-        // Register hook points
-        hook_index_pkt4_receive_   = Hooks.hook_index_pkt4_receive_;
-        hook_index_subnet4_select_ = Hooks.hook_index_subnet4_select_;
-        hook_index_pkt4_send_      = Hooks.hook_index_pkt4_send_;
-
         /// @todo call loadLibraries() when handling configuration changes
 
     } catch (const std::exception &e) {
@@ -360,6 +381,13 @@ Dhcpv4Srv::~Dhcpv4Srv() {
         LOG_ERROR(dhcp4_logger, DHCP4_SRV_D2STOP_ERROR).arg(ex.what());
     }
 
+    try {
+        Dhcp4to6Ipc::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();
 
     // The lease manager was instantiated during DHCPv4Srv configuration,
@@ -379,6 +407,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;
@@ -428,7 +461,111 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& query) const {
     subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet(selector);
 
     // Let's execute all callouts registered for subnet4_select
-    if (HooksManager::calloutsPresent(hook_index_subnet4_select_)) {
+    if (HooksManager::calloutsPresent(Hooks.hook_index_subnet4_select_)) {
+        CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+        // We're reusing callout_handle from previous calls
+        callout_handle->deleteAllArguments();
+
+        // Set new arguments
+        callout_handle->setArgument("query4", query);
+        callout_handle->setArgument("subnet4", subnet);
+        callout_handle->setArgument("subnet4collection",
+                                    cfgmgr.getCurrentCfg()->
+                                    getCfgSubnets4()->getAll());
+
+        // Call user (and server-side) callouts
+        HooksManager::callCallouts(Hooks.hook_index_subnet4_select_,
+                                   *callout_handle);
+
+        // Callouts decided to skip this step. This means that no subnet
+        // will be selected. Packet processing will continue, but it will
+        // be severely limited (i.e. only global options will be assigned)
+        if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
+            LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
+                      DHCP4_HOOK_SUBNET4_SELECT_SKIP)
+                .arg(query->getLabel());
+            return (Subnet4Ptr());
+        }
+
+        /// @todo: Add support for DROP status
+
+        // Use whatever subnet was specified by the callout
+        callout_handle->getArgument("subnet4", subnet);
+    }
+
+    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);
+}
+
+isc::dhcp::Subnet4Ptr
+Dhcpv4Srv::selectSubnet4o6(const Pkt4Ptr& query) const {
+
+    Subnet4Ptr subnet;
+
+    SubnetSelector selector;
+    selector.ciaddr_ = query->getCiaddr();
+    selector.giaddr_ = query->getGiaddr();
+    selector.local_address_ = query->getLocalAddr();
+    selector.client_classes_ = query->classes_;
+    selector.iface_name_ = query->getIface();
+    // Mark it as DHCPv4-over-DHCPv6
+    selector.dhcp4o6_ = true;
+    // Now the DHCPv6 part
+    selector.remote_address_ = query->getRemoteAddr();
+    selector.first_relay_linkaddr_ = IOAddress("::");
+
+    // Handle a DHCPv6 relayed query
+    Pkt4o6Ptr query4o6 = boost::dynamic_pointer_cast<Pkt4o6>(query);
+    if (!query4o6) {
+        isc_throw(Unexpected, "Can't get DHCP4o6 message");
+    }
+    const Pkt6Ptr& query6 = query4o6->getPkt6();
+
+    // Initialize fields specific to relayed messages.
+    if (query6 && !query6->relay_info_.empty()) {
+        BOOST_REVERSE_FOREACH(Pkt6::RelayInfo relay, query6->relay_info_) {
+            if (!relay.linkaddr_.isV6Zero() &&
+                !relay.linkaddr_.isV6LinkLocal()) {
+                selector.first_relay_linkaddr_ = relay.linkaddr_;
+                break;
+            }
+        }
+        selector.interface_id_ =
+            query6->getAnyRelayOption(D6O_INTERFACE_ID,
+                                      Pkt6::RELAY_GET_FIRST);
+    }
+
+    // If the Subnet Selection option is present, extract its value.
+    OptionPtr sbnsel = query->getOption(DHO_SUBNET_SELECTION);
+    if (sbnsel) {
+        OptionCustomPtr oc = boost::dynamic_pointer_cast<OptionCustom>(sbnsel);
+        if (oc) {
+            selector.option_select_ = oc->readAddress();
+        }
+    }
+
+    CfgMgr& cfgmgr = CfgMgr::instance();
+    subnet = cfgmgr.getCurrentCfg()->getCfgSubnets4()->selectSubnet4o6(selector);
+
+    // Let's execute all callouts registered for subnet4_select
+    if (HooksManager::calloutsPresent(Hooks.hook_index_subnet4_select_)) {
         CalloutHandlePtr callout_handle = getCalloutHandle(query);
 
         // We're reusing callout_handle from previous calls
@@ -442,7 +579,7 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& query) const {
                                     getCfgSubnets4()->getAll());
 
         // Call user (and server-side) callouts
-        HooksManager::callCallouts(hook_index_subnet4_select_,
+        HooksManager::callCallouts(Hooks.hook_index_subnet4_select_,
                                    *callout_handle);
 
         // Callouts decided to skip this step. This means that no subnet
@@ -751,7 +888,7 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
         .arg(query->toText());
 
     // Let's execute all callouts registered for pkt4_receive
-    if (HooksManager::calloutsPresent(hook_index_pkt4_receive_)) {
+    if (HooksManager::calloutsPresent(Hooks.hook_index_pkt4_receive_)) {
         CalloutHandlePtr callout_handle = getCalloutHandle(query);
 
         // Delete previously set arguments
@@ -761,7 +898,7 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
         callout_handle->setArgument("query4", query);
 
         // Call callouts
-        HooksManager::callCallouts(hook_index_pkt4_receive_,
+        HooksManager::callCallouts(Hooks.hook_index_pkt4_receive_,
                                    *callout_handle);
 
         // Callouts decided to skip the next processing step. The next
@@ -837,7 +974,7 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
     bool skip_pack = false;
 
     // Execute all callouts registered for pkt4_send
-    if (HooksManager::calloutsPresent(hook_index_pkt4_send_)) {
+    if (HooksManager::calloutsPresent(Hooks.hook_index_pkt4_send_)) {
         CalloutHandlePtr callout_handle = getCalloutHandle(query);
 
         // Delete all previous arguments
@@ -853,7 +990,7 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
         callout_handle->setArgument("query4", query);
 
         // Call all installed callouts
-        HooksManager::callCallouts(hook_index_pkt4_send_,
+        HooksManager::callCallouts(Hooks.hook_index_pkt4_send_,
                                    *callout_handle);
 
         // Callouts decided to skip the next processing step. The next
@@ -1641,7 +1778,8 @@ 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.isV4Bcast()) {
+    // Do the same for DHCPv4-over-DHCPv6 exchanges.
+    if (local_addr.isV4Bcast() || query->isDhcp4o6()) {
         SocketInfo sock_info = IfaceMgr::instance().getSocket(*query);
         local_addr = sock_info.addr_;
     }
@@ -1673,6 +1811,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
@@ -2194,6 +2338,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.
@@ -2624,5 +2774,33 @@ void Dhcpv4Srv::processStatsSent(const Pkt4Ptr& response) {
                                               static_cast<int64_t>(1));
 }
 
+int Dhcpv4Srv::getHookIndexBuffer4Receive() {
+    return (Hooks.hook_index_buffer4_receive_);
+}
+
+int Dhcpv4Srv::getHookIndexPkt4Receive() {
+    return (Hooks.hook_index_pkt4_receive_);
+}
+
+int Dhcpv4Srv::getHookIndexSubnet4Select() {
+    return (Hooks.hook_index_subnet4_select_);
+}
+
+int Dhcpv4Srv::getHookIndexLease4Release() {
+    return (Hooks.hook_index_lease4_release_);
+}
+
+int Dhcpv4Srv::getHookIndexPkt4Send() {
+    return (Hooks.hook_index_pkt4_send_);
+}
+
+int Dhcpv4Srv::getHookIndexBuffer4Send() {
+    return (Hooks.hook_index_buffer4_send_);
+}
+
+int Dhcpv4Srv::getHookIndexLease4Decline() {
+    return (Hooks.hook_index_lease4_decline_);
+}
+
 }   // namespace dhcp
 }   // namespace isc

+ 44 - 7
src/bin/dhcp4/dhcp4_srv.h

@@ -81,6 +81,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_);
@@ -713,6 +718,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_;
@@ -777,21 +788,47 @@ private:
     /// @return Option that contains netmask information
     static OptionPtr getNetmaskOption(const Subnet4Ptr& subnet);
 
+    uint16_t port_;  ///< UDP port number on which server listens.
+    bool use_bcast_; ///< Should broadcast be enabled on sockets (if true).
+
+public:
+    /// Class methods for DHCPv4-over-DHCPv6 handler
+
     /// @brief Updates statistics for received packets
     /// @param query packet received
     static void processStatsReceived(const Pkt4Ptr& query);
 
     /// @brief Updates statistics for transmitted packets
-    /// @param query packet transmitted
+    /// @param response packet transmitted
     static void processStatsSent(const Pkt4Ptr& response);
 
-    uint16_t port_;  ///< UDP port number on which server listens.
-    bool use_bcast_; ///< Should broadcast be enabled on sockets (if true).
+    /// @brief Returns the index for "buffer4_receive" hook point
+    /// @return the index for "buffer4_receive" hook point
+    static int getHookIndexBuffer4Receive();
+
+    /// @brief Returns the index for "pkt4_receive" hook point
+    /// @return the index for "pkt4_receive" hook point
+    static int getHookIndexPkt4Receive();
+
+    /// @brief Returns the index for "subnet4_select" hook point
+    /// @return the index for "subnet4_select" hook point
+    static int getHookIndexSubnet4Select();
+
+    /// @brief Returns the index for "lease4_release" hook point
+    /// @return the index for "lease4_release" hook point
+    static int getHookIndexLease4Release();
+
+    /// @brief Returns the index for "pkt4_send" hook point
+    /// @return the index for "pkt4_send" hook point
+    static int getHookIndexPkt4Send();
+
+    /// @brief Returns the index for "buffer4_send" hook point
+    /// @return the index for "buffer4_send" hook point
+    static int getHookIndexBuffer4Send();
 
-    /// Indexes for registered hook points
-    int hook_index_pkt4_receive_;
-    int hook_index_subnet4_select_;
-    int hook_index_pkt4_send_;
+    /// @brief Returns the index for "lease4_decline" hook point
+    /// @return the index for "lease4_decline" hook point
+    static int getHookIndexLease4Decline();
 };
 
 }; // namespace isc::dhcp

+ 116 - 17
src/bin/dhcp4/dhcp4to6_ipc.cc

@@ -8,10 +8,21 @@
 
 #include <util/buffer.h>
 #include <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt4o6.h>
+#include <dhcp/pkt6.h>
+#include <dhcpsrv/callout_handle_store.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/dhcp4to6_ipc.h>
+#include <dhcp4/dhcp4_log.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_log.h>
+#include <hooks/hooks_manager.h>
 
 using namespace std;
+using namespace isc::dhcp;
+using namespace isc::hooks;
 
 namespace isc {
 namespace dhcp {
@@ -44,13 +55,25 @@ void Dhcp4to6Ipc::open() {
 
 void Dhcp4to6Ipc::handler() {
     Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance();
+    Pkt6Ptr pkt;
+
+    try {
+        LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_DHCP4O6_RECEIVING);
+        // Receive message from the IPC socket.
+        pkt = ipc.receive();
+
+        // from Dhcpv4Srv::run_one() after receivePacket()
+        if (pkt) {
+            LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC, DHCP6_DHCP4O6_PACKET_RECEIVED)
+                .arg(static_cast<int>(pkt->getType()))
+                .arg(pkt->getRemoteAddr().toText())
+                .arg(pkt->getIface());
+        }
+    } catch (const std::exception& e) {
+        LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_DHCP4O6_RECEIVE_FAIL)
+            .arg(e.what());
+    }
 
-    // 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;
     }
@@ -58,25 +81,101 @@ void Dhcp4to6Ipc::handler() {
     // 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");
+        LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_DHCP4O6_BAD_PACKET)
+            .arg("DHCPv4 message option not present");
+        return;
     } else if (msgs.size() > 1) {
-        isc_throw(Dhcp4o6IpcError, "expected exactly one DHCPv4 message within"
-                  " DHCPv4 message option received by the DHCPv4 server");
+        LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_DHCP4O6_BAD_PACKET)
+            .arg("more than one DHCPv4 message option");
+        return;
     }
 
+    // Get the DHCPv4 message 
     OptionPtr msg = msgs.begin()->second;
     if (!msg) {
-        isc_throw(Dhcp4o6IpcError, "null DHCPv4 message option in the"
-                  " DHCPv4o6 message received by the DHCPv4 server");
+        LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL, DHCP4_DHCP4O6_BAD_PACKET)
+            .arg("null DHCPv4 message option");
+        return;
     }
 
-    // Record this message.
-    ipc.received_.reset(new Pkt4o6(msg->getData(), pkt));
-}
+    // Extract the DHCPv4 packet with DHCPv6 packet attached
+    Pkt4Ptr query(new Pkt4o6(msg->getData(), pkt));
 
-Pkt4o6Ptr& Dhcp4to6Ipc::getReceived() {
-    return (received_);
+    // From Dhcpv4Srv::run_one() processing and after
+    Pkt4Ptr rsp;
+
+    ControlledDhcpv4Srv::getInstance()->processPacket(query, rsp);
+
+    if (!rsp) {
+        return;
+    }
+
+    try {
+        // Now all fields and options are constructed into output wire buffer.
+        // Option objects modification does not make sense anymore. Hooks
+        // can only manipulate wire buffer at this stage.
+        // Let's execute all callouts registered for buffer4_send
+      if (HooksManager::calloutsPresent(Dhcpv4Srv::getHookIndexBuffer4Send())) {
+            CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+            // Delete previously set arguments
+            callout_handle->deleteAllArguments();
+
+            // Pass incoming packet as argument
+            callout_handle->setArgument("response4", rsp);
+
+            // Call callouts
+            HooksManager::callCallouts(Dhcpv4Srv::getHookIndexBuffer4Send(),
+                                       *callout_handle);
+
+            // Callouts decided to skip the next processing step. The next
+            // processing step would to parse the packet, so skip at this
+            // stage means drop.
+            if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_SKIP) {
+                LOG_DEBUG(hooks_logger, DBG_DHCP4_HOOKS,
+                          DHCP4_HOOK_BUFFER_SEND_SKIP)
+                    .arg(rsp->getLabel());
+                return;
+            }
+
+            /// @todo: Add support for DROP status.
+
+            callout_handle->getArgument("response4", rsp);
+        }
+
+        Pkt4o6Ptr rsp6 = boost::dynamic_pointer_cast<Pkt4o6>(rsp);
+        // Should not happen
+        if (!rsp6) {
+            isc_throw(Unexpected, "Dhcp4o6 packet cast fail");
+        }
+
+        LOG_DEBUG(packet4_logger, DBG_DHCP4_BASIC, DHCP4_DHCP4O6_PACKET_SEND)
+            .arg(rsp6->getLabel())
+            .arg(rsp6->getName())
+            .arg(static_cast<int>(rsp6->getType()))
+            .arg(rsp6->getRemoteAddr())
+            .arg(rsp6->getIface())
+            .arg(rsp->getLabel())
+            .arg(rsp->getName())
+            .arg(static_cast<int>(rsp->getType()));
+
+        LOG_DEBUG(packet4_logger, DBG_DHCP4_DETAIL_DATA,
+                  DHCP4_DHCP4O6_RESPONSE_DATA)
+            .arg(rsp6->getLabel())
+            .arg(rsp6->getName())
+            .arg(static_cast<int>(rsp6->getType()))
+            .arg(rsp6->toText());
+
+        ipc.send(rsp6->getPkt6());
+
+        // Update statistics accordingly for sent packet.
+        Dhcpv4Srv::processStatsSent(rsp);
+
+    } catch (const std::exception& e) {
+        LOG_ERROR(packet4_logger, DHCP4_DHCP4O6_PACKET_SEND_FAIL)
+            .arg(rsp->getLabel())
+            .arg(e.what());
+    }
 }
 
 };  // namespace dhcp

+ 0 - 10
src/bin/dhcp4/dhcp4to6_ipc.h

@@ -48,16 +48,6 @@ public:
     /// 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

+ 183 - 9
src/bin/dhcp4/tests/dhcp4to6_ipc_unittest.cc

@@ -5,13 +5,20 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 #include <config.h>
+
 #include <asiolink/io_address.h>
 #include <dhcp/pkt4o6.h>
 #include <dhcp/pkt6.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/dhcp4to6_ipc.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/testutils/dhcp4o6_test_ipc.h>
+#include <stats/stats_mgr.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_manager.h>
+
 #include <gtest/gtest.h>
 #include <stdint.h>
 
@@ -19,6 +26,8 @@ using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
+using namespace isc::stats;
+using namespace isc::hooks;
 using namespace isc::util;
 
 namespace {
@@ -30,7 +39,7 @@ const uint16_t TEST_PORT = 32000;
 typedef Dhcp4o6TestIpc TestIpc;
 
 /// @brief Test fixture class for DHCPv4 endpoint of DHCPv4o6 IPC.
-class Dhcp4to6IpcTest : public ::testing::Test {
+class Dhcp4to6IpcTest : public Dhcpv4SrvTest {
 public:
 
     /// @brief Constructor
@@ -38,8 +47,23 @@ public:
     /// Configures IPC to use a test port. It also provides a fake
     /// configuration of interfaces.
     Dhcp4to6IpcTest()
-        : iface_mgr_test_config_(true) {
+        : Dhcpv4SrvTest(),
+        iface_mgr_test_config_(true) {
+        IfaceMgr::instance().openSockets4();
         configurePort(TEST_PORT);
+        // Install buffer4_receive_callout
+        EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().
+                        registerCallout("buffer4_receive",
+                                        buffer4_receive_callout));
+        // Install buffer4_send_callout
+        EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().
+                        registerCallout("buffer4_send", buffer4_send_callout));
+        // Verify we have a controlled server
+        ControlledDhcpv4Srv* srv = NULL;
+        EXPECT_NO_THROW(srv = ControlledDhcpv4Srv::getInstance());
+        EXPECT_TRUE(srv);
+        // Let's wipe all existing statistics.
+        StatsMgr::instance().removeAll();
     }
 
     /// @brief Configure DHCP4o6 port.
@@ -55,6 +79,42 @@ public:
     /// @return Pointer to the instance of the DHCPv4-query Message option.
     OptionPtr createDHCPv4MsgOption() const;
 
+    /// @brief Handler for the buffer4_receive hook
+    ///
+    /// This hook is at the beginning of processPacket
+    ///
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+        buffer4_receive_callout(CalloutHandle& callout_handle) {
+        callout_handle.getArgument("query4", callback_recv_pkt_);
+        return (0);
+    }
+
+    /// @brief Handler for the buffer4_send hook
+    ///
+    /// This hook is at the end of the DHCPv4o6 packet handler
+    ///
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+        buffer4_send_callout(CalloutHandle& callout_handle) {
+        callout_handle.getArgument("response4", callback_sent_pkt_);
+        return (0);
+    }
+
+    /// @brief Response Pkt4 shared pointer returned in the receive callout
+    static Pkt4Ptr callback_recv_pkt_;
+
+    /// @brief Response Pkt4 shared pointer returned in the send callout
+    static Pkt4Ptr callback_sent_pkt_;
+
+    /// @brief reference to a controlled server
+    ///
+    /// Dhcp4to6Ipc::handler() uses the instance of the controlled server
+    /// so it has to be build. This reference does this.
+    ControlledDhcpv4Srv srv_;
+
 private:
 
     /// @brief Provides fake configuration of interfaces.
@@ -62,6 +122,9 @@ private:
 
 };
 
+Pkt4Ptr Dhcp4to6IpcTest::callback_recv_pkt_;
+Pkt4Ptr Dhcp4to6IpcTest::callback_sent_pkt_;
+
 void
 Dhcp4to6IpcTest::configurePort(uint16_t port) {
     CfgMgr::instance().getStagingCfg()->setDhcp4o6Port(port);
@@ -110,12 +173,20 @@ TEST_F(Dhcp4to6IpcTest, receive) {
     pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
     ASSERT_NO_THROW(pkt->pack());
 
+    // Reset the received packet
+    Dhcp4to6IpcTest::callback_recv_pkt_.reset();
+
     // Send and wait up to 1 second to receive it.
     ASSERT_NO_THROW(src_ipc.send(pkt));
     ASSERT_NO_THROW(IfaceMgr::instance().receive6(1, 0));
 
     // Make sure that the message has been received.
-    Pkt4o6Ptr pkt_received = ipc.getReceived();
+    // The buffer4_receive hook is at the beginning of processPacket
+    // so this proves it was passed to it.
+    Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_;
+    ASSERT_TRUE(pkt4_received);
+    Pkt4o6Ptr pkt_received =
+        boost::dynamic_pointer_cast<Pkt4o6>(pkt4_received);
     ASSERT_TRUE(pkt_received);
     Pkt6Ptr pkt6_received = pkt_received->getPkt6();
     ASSERT_TRUE(pkt6_received);
@@ -144,10 +215,16 @@ TEST_F(Dhcp4to6IpcTest, receiveMultipleQueries) {
     pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
     ASSERT_NO_THROW(pkt->pack());
 
-    // Send message.
+    // Reset the received packet
+    Dhcp4to6IpcTest::callback_recv_pkt_.reset();
+
+    // Send and wait up to 1 second to receive it.
     ASSERT_NO_THROW(src_ipc.send(pkt));
-    // Reception handler should throw exception.
-    EXPECT_THROW(IfaceMgr::instance().receive6(1, 0), Dhcp4o6IpcError);
+    EXPECT_NO_THROW(IfaceMgr::instance().receive6(1, 0));
+
+    // No message should has been sent.
+    Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_;
+    EXPECT_FALSE(pkt4_received);
 }
 
 // This test verifies that message with no DHCPv4 query options is rejected.
@@ -167,10 +244,107 @@ TEST_F(Dhcp4to6IpcTest, receiveNoQueries) {
     pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
     ASSERT_NO_THROW(pkt->pack());
 
-    // Send message.
+    // Reset the received packet
+    Dhcp4to6IpcTest::callback_recv_pkt_.reset();
+
+    // Send and wait up to 1 second to receive it.
+    ASSERT_NO_THROW(src_ipc.send(pkt));
+    EXPECT_NO_THROW(IfaceMgr::instance().receive6(1, 0));
+
+    // No message should has been sent.
+    Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_;
+    EXPECT_FALSE(pkt4_received);
+}
+
+// This test verifies that the DHCPv4 endpoint of the DHCPv4o6 IPC can
+// process messages.
+TEST_F(Dhcp4to6IpcTest, process) {
+    // Create instance of the IPC endpoint under test.
+    Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance();
+    // Create instance of the IPC endpoint being used as a source of messages.
+    TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6);
+
+    // Open both endpoints.
+    ASSERT_NO_THROW(ipc.open());
+    ASSERT_NO_THROW(src_ipc.open());
+
+    // Get statistics
+    StatsMgr& mgr = StatsMgr::instance();
+    ObservationPtr pkt4_snd = mgr.getObservation("pkt4-sent");
+    ObservationPtr pkt4_ack = mgr.getObservation("pkt4-ack-sent");
+    EXPECT_FALSE(pkt4_snd);
+    EXPECT_FALSE(pkt4_ack);
+
+    // Create an information request message
+    Pkt4Ptr infreq(new Pkt4(DHCPINFORM, 1234));
+    infreq->setHWAddr(generateHWAddr(6));
+    infreq->setCiaddr(IOAddress("192.0.1.2"));
+    // Make a wire representation of the DHCPv4 message.
+    infreq->pack();
+    OutputBuffer& output_buffer = infreq->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));
+
+    // Create message to be sent over IPC.
+    Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 1234));
+    pkt->addOption(opt_msg);
+    pkt->setIface("eth0");
+    pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
+    ASSERT_NO_THROW(pkt->pack());
+
+    // Reset the received packet
+    Dhcp4to6IpcTest::callback_recv_pkt_.reset();
+
+    // Send and wait up to 1 second to receive it.
     ASSERT_NO_THROW(src_ipc.send(pkt));
-    // Reception handler should throw exception.
-    EXPECT_THROW(IfaceMgr::instance().receive6(1, 0), Dhcp4o6IpcError);
+    ASSERT_NO_THROW(IfaceMgr::instance().receive6(1, 0));
+
+    // Make sure that the message has been received.
+    Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_;
+    ASSERT_TRUE(pkt4_received);
+    Pkt4o6Ptr pkt_received =
+        boost::dynamic_pointer_cast<Pkt4o6>(pkt4_received);
+    ASSERT_TRUE(pkt_received);
+    Pkt6Ptr pkt6_received = pkt_received->getPkt6();
+    ASSERT_TRUE(pkt6_received);
+    EXPECT_EQ("eth0", pkt6_received->getIface());
+    EXPECT_EQ("2001:db8:1::123", pkt6_received->getRemoteAddr().toText());
+
+    // Make sure that the message has been processed.
+    // Using the buffer4_send hook
+    Pkt4Ptr pkt4_sent = Dhcp4to6IpcTest::callback_sent_pkt_;
+    ASSERT_TRUE(pkt4_sent);
+    EXPECT_EQ(DHCPACK, pkt4_sent->getType());
+    Pkt4o6Ptr pkt_sent = boost::dynamic_pointer_cast<Pkt4o6>(pkt4_sent);
+    ASSERT_TRUE(pkt_sent);
+    Pkt6Ptr pkt6_sent = pkt_sent->getPkt6();
+    ASSERT_TRUE(pkt6_sent);
+    EXPECT_EQ(DHCPV6_DHCPV4_RESPONSE, pkt6_sent->getType());
+    EXPECT_EQ("eth0", pkt6_sent->getIface());
+    EXPECT_EQ("2001:db8:1::123", pkt6_sent->getRemoteAddr().toText());
+
+    // Verify the 4o6 part
+    OptionCollection sent_msgs = pkt6_sent->getOptions(D6O_DHCPV4_MSG);
+    ASSERT_EQ(1, sent_msgs.size());
+    OptionPtr sent_msg = sent_msgs.begin()->second;
+    ASSERT_TRUE(sent_msg);
+    const OptionBuffer sent_buf = sent_msg->getData();
+    Pkt4Ptr pkt4_opt;
+    ASSERT_NO_THROW(pkt4_opt.reset(new Pkt4(&sent_buf[0], sent_buf.size())));
+    ASSERT_NO_THROW(pkt4_opt->unpack());
+    EXPECT_EQ(DHCPACK, pkt4_sent->getType());
+    EXPECT_EQ(pkt4_sent->len(), pkt4_opt->len());
+
+    // Verify statistics
+    pkt4_snd = mgr.getObservation("pkt4-sent");
+    pkt4_ack = mgr.getObservation("pkt4-ack-sent");
+    ASSERT_TRUE(pkt4_snd);
+    ASSERT_TRUE(pkt4_ack);
+    EXPECT_EQ(1, pkt4_snd->getInteger().first);
+    EXPECT_EQ(1, pkt4_ack->getInteger().first);
 }
 
 } // end of anonymous namespace

+ 0 - 4
src/lib/dhcpsrv/cfg_subnets4.cc

@@ -76,10 +76,6 @@ CfgSubnets4::selectSubnet4o6(const SubnetSelector& selector) const {
 Subnet4Ptr
 CfgSubnets4::selectSubnet(const SubnetSelector& selector) const {
 
-    if (selector.dhcp4o6_) {
-        return selectSubnet4o6(selector);
-    }
-
     // First use RAI link select sub-option or subnet select option
     if (!selector.option_select_.isV4Zero()) {
         return (selectSubnet(selector.option_select_,

+ 10 - 14
src/lib/dhcpsrv/dhcp4o6_ipc.cc

@@ -156,9 +156,7 @@ Pkt6Ptr Dhcp4o6IpcBase::receive() {
     if (!option_vendor) {
         LOG_WARN(dhcpsrv_logger, DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET)
             .arg("no ISC vendor option");
-        isc_throw(Dhcp4o6IpcError, "option " << D6O_VENDOR_OPTS
-                  << " with ISC enterprise id is not present in the DHCP4o6"
-                  " message sent between the servers");
+        isc_throw(Dhcp4o6IpcError, "malformed packet (no ISC vendor option)");
     }
 
     // The option carrying interface name is required.
@@ -167,10 +165,9 @@ Pkt6Ptr Dhcp4o6IpcBase::receive() {
     if (!ifname) {
         LOG_WARN(dhcpsrv_logger, DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET)
             .arg("no interface suboption");
-        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");
+        isc_throw(Dhcp4o6IpcError,
+                  "malformed packet (interface suboption missing "
+                  "or has incorrect type)");
     }
 
     // Check if this interface is present in the system.
@@ -178,9 +175,9 @@ Pkt6Ptr Dhcp4o6IpcBase::receive() {
     if (!iface) {
         LOG_WARN(dhcpsrv_logger, DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET)
             .arg("can't get interface " + ifname->getValue());
-        isc_throw(Dhcp4o6IpcError, "option " << ISC_V6_4O6_INTERFACE
-                  << " sent in the DHCP4o6 message contains non-existing"
-                  " interface name '" << ifname->getValue() << "'");
+        isc_throw(Dhcp4o6IpcError,
+                  "malformed packet (unknown interface "
+                  + ifname->getValue() + ")");
     }
 
     // Get the option holding source IPv6 address.
@@ -189,10 +186,9 @@ Pkt6Ptr Dhcp4o6IpcBase::receive() {
     if (!srcs) {
         LOG_WARN(dhcpsrv_logger, DHCPSRV_DHCP4O6_RECEIVED_BAD_PACKET)
             .arg("no source address suboption");
-        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");
+        isc_throw(Dhcp4o6IpcError,
+                  "malformed packet (source address suboption missing "
+                  "or has incorrect type)");
     }
 
     // Update the packet.

+ 5 - 5
src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc

@@ -375,7 +375,7 @@ TEST(CfgSubnets4Test, 4o6subnetMatchByAddress) {
     selector.dhcp4o6_ = true;
     selector.remote_address_ = IOAddress("2001:db8:1::dead:beef");
 
-    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    EXPECT_EQ(subnet2, cfg.selectSubnet4o6(selector));
 }
 
 // This test checks if the IPv4 subnet can be selected based on the value of
@@ -405,11 +405,11 @@ TEST(CfgSubnets4Test, 4o6subnetMatchByInterfaceId) {
     selector.dhcp4o6_ = true;
     selector.interface_id_ = interfaceId2;
     // We have mismatched interface-id options (data1 vs data2). Should not match.
-    EXPECT_FALSE(cfg.selectSubnet(selector));
+    EXPECT_FALSE(cfg.selectSubnet4o6(selector));
 
     // This time we have correct interface-id. Should match.
     selector.interface_id_ = interfaceId1;
-    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    EXPECT_EQ(subnet2, cfg.selectSubnet4o6(selector));
 }
 
 // This test checks if the IPv4 subnet can be selected based on the value of
@@ -431,11 +431,11 @@ TEST(CfgSubnets4Test, 4o6subnetMatchByInterfaceName) {
     selector.dhcp4o6_ = true;
     selector.iface_name_ = "eth5";
     // We have mismatched interface names. Should not match.
-    EXPECT_FALSE(cfg.selectSubnet(selector));
+    EXPECT_FALSE(cfg.selectSubnet4o6(selector));
 
     // This time we have correct names. Should match.
     selector.iface_name_ = "eth7";
-    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    EXPECT_EQ(subnet2, cfg.selectSubnet4o6(selector));
 }