Browse Source

[2994] pkt4_receive, pkt4_send, subnet4_select implemented.

Tomek Mrugalski 11 years ago
parent
commit
d3c480a5ef

+ 3 - 0
src/bin/dhcp4/dhcp4_log.h

@@ -38,6 +38,9 @@ const int DBG_DHCP4_COMMAND = DBGLVL_COMMAND;
 // Trace basic operations within the code.
 // Trace basic operations within the code.
 const int DBG_DHCP4_BASIC = DBGLVL_TRACE_BASIC;
 const int DBG_DHCP4_BASIC = DBGLVL_TRACE_BASIC;
 
 
+// Trace hook related operations
+const int DBG_DHCP4_HOOKS = DBGLVL_TRACE_BASIC;
+
 // Trace detailed operations, including errors raised when processing invalid
 // Trace detailed operations, including errors raised when processing invalid
 // packets.  (These are not logged at severities of WARN or higher for fear
 // packets.  (These are not logged at severities of WARN or higher for fear
 // that a set of deliberately invalid packets set to the server could overwhelm
 // that a set of deliberately invalid packets set to the server could overwhelm

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

@@ -60,6 +60,25 @@ This informational message is printed every time DHCPv4 server is started
 and gives both the type and name of the database being used to store
 and gives both the type and name of the database being used to store
 lease and other information.
 lease and other information.
 
 
+% DHCP4_HOOK_PACKET_RCVD_SKIP received DHCPv4 packet was dropped, because a callout set skip flag.
+This debug message is printed when a callout installed on pkt4_receive
+hook point sets skip flag. For this particular hook point, the setting
+of the flag by a callout instructs the server to drop the packet.
+
+% DHCP4_HOOK_PACKET_SEND_SKIP Prepared DHCPv6 response was not sent, because a callout set skip flag.
+This debug message is printed when a callout installed on pkt4_send
+hook point sets skip flag. For this particular hook point, the setting
+of the flag by a callout instructs the server to drop the packet. This
+effectively means that the client will not get any response, even though
+the server processed client's request and acted on it (e.g. possibly
+allocated a lease).
+
+% DHCP4_HOOK_SUBNET4_SELECT_SKIP No subnet was selected, because a callout set skip flag.
+This debug message is printed when a callout installed on subnet4_select
+hook point sets a skip flag. It means that the server was told that no subnet
+should be selected. This severely limits further processing - server will be only
+able to offer global options. No addresses will be assigned.
+
 % DHCP4_LEASE_ADVERT lease %1 advertised (client client-id %2, hwaddr %3)
 % DHCP4_LEASE_ADVERT lease %1 advertised (client client-id %2, hwaddr %3)
 This debug message indicates that the server successfully advertised
 This debug message indicates that the server successfully advertised
 a lease. It is up to the client to choose one server out of othe advertised
 a lease. It is up to the client to choose one server out of othe advertised

+ 157 - 5
src/bin/dhcp4/dhcp4_srv.cc

@@ -30,6 +30,8 @@
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/utils.h>
 #include <dhcpsrv/utils.h>
 #include <dhcpsrv/addr_utilities.h>
 #include <dhcpsrv/addr_utilities.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/callout_handle.h>
 
 
 #include <boost/algorithm/string/erase.hpp>
 #include <boost/algorithm/string/erase.hpp>
 
 
@@ -39,9 +41,30 @@
 using namespace isc;
 using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
+using namespace isc::hooks;
 using namespace isc::log;
 using namespace isc::log;
 using namespace std;
 using namespace std;
 
 
+/// Structure that holds registered hook indexes
+struct Dhcp6Hooks {
+    int hook_index_pkt4_receive_;   ///< index for "pkt4_receive" hook point
+    int hook_index_subnet4_select_; ///< index for "subnet4_select" hook point
+    int hook_index_pkt4_send_;      ///< index for "pkt4_send" hook point
+
+    /// Constructor that registers hook points for DHCPv6 engine
+    Dhcp6Hooks() {
+        hook_index_pkt4_receive_   = HooksManager::registerHook("pkt4_receive");
+        hook_index_subnet4_select_ = HooksManager::registerHook("subnet4_select");
+        hook_index_pkt4_send_      = HooksManager::registerHook("pkt4_send");
+    }
+};
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+Dhcp6Hooks Hooks;
+
 namespace isc {
 namespace isc {
 namespace dhcp {
 namespace dhcp {
 
 
@@ -58,7 +81,9 @@ static const char* SERVER_ID_FILE = "b10-dhcp4-serverid";
 // grants those options and a single, fixed, hardcoded lease.
 // grants those options and a single, fixed, hardcoded lease.
 
 
 Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
 Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
-                     const bool direct_response_desired) {
+                     const bool direct_response_desired)
+    :serverid_(), shutdown_(true), alloc_engine_(), hook_index_pkt4_receive_(-1),
+     hook_index_subnet4_select_(-1), hook_index_pkt4_send_(-1) {
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
     try {
     try {
         // First call to instance() will create IfaceMgr (it's a singleton)
         // First call to instance() will create IfaceMgr (it's a singleton)
@@ -103,6 +128,16 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
         // Instantiate allocation engine
         // Instantiate allocation engine
         alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
         alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
 
 
+        // 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
+        vector<string> libraries; // no libraries at this time
+        HooksManager::loadLibraries(libraries);
+
+
     } catch (const std::exception &e) {
     } catch (const std::exception &e) {
         LOG_ERROR(dhcp4_logger, DHCP4_SRV_CONSTRUCT_ERROR).arg(e.what());
         LOG_ERROR(dhcp4_logger, DHCP4_SRV_CONSTRUCT_ERROR).arg(e.what());
         shutdown_ = true;
         shutdown_ = true;
@@ -122,6 +157,14 @@ Dhcpv4Srv::shutdown() {
     shutdown_ = true;
     shutdown_ = true;
 }
 }
 
 
+Pkt4Ptr Dhcpv4Srv::receivePacket(int timeout) {
+    return (IfaceMgr::instance().receive4(timeout));
+}
+
+void Dhcpv4Srv::sendPacket(const Pkt4Ptr& packet) {
+    IfaceMgr::instance().send(packet);
+}
+
 bool
 bool
 Dhcpv4Srv::run() {
 Dhcpv4Srv::run() {
     while (!shutdown_) {
     while (!shutdown_) {
@@ -134,7 +177,7 @@ Dhcpv4Srv::run() {
         Pkt4Ptr rsp;
         Pkt4Ptr rsp;
 
 
         try {
         try {
-            query = IfaceMgr::instance().receive4(timeout);
+            query = receivePacket(timeout);
         } catch (const std::exception& e) {
         } catch (const std::exception& e) {
             LOG_ERROR(dhcp4_logger, DHCP4_PACKET_RECEIVE_FAIL).arg(e.what());
             LOG_ERROR(dhcp4_logger, DHCP4_PACKET_RECEIVE_FAIL).arg(e.what());
         }
         }
@@ -156,6 +199,31 @@ Dhcpv4Srv::run() {
             LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_QUERY_DATA)
             LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_QUERY_DATA)
                       .arg(query->toText());
                       .arg(query->toText());
 
 
+            // Let's execute all callouts registered for packet_received
+            if (HooksManager::getHooksManager().calloutsPresent(hook_index_pkt4_receive_)) {
+                CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+                // Delete previously set arguments
+                callout_handle->deleteAllArguments();
+
+                // Pass incoming packet as argument
+                callout_handle->setArgument("query4", query);
+
+                // Call callouts
+                HooksManager::getHooksManager().callCallouts(hook_index_pkt4_receive_,
+                                                *callout_handle);
+
+                // Callouts decided to skip the next processing step. The next
+                // processing step would to process the packet, so skip at this
+                // stage means drop.
+                if (callout_handle->getSkip()) {
+                    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_HOOKS, DHCP4_HOOK_PACKET_RCVD_SKIP);
+                    continue;
+                }
+
+                callout_handle->getArgument("query4", query);
+            }
+
             try {
             try {
                 switch (query->getType()) {
                 switch (query->getType()) {
                 case DHCPDISCOVER:
                 case DHCPDISCOVER:
@@ -220,13 +288,39 @@ Dhcpv4Srv::run() {
                 rsp->setIface(query->getIface());
                 rsp->setIface(query->getIface());
                 rsp->setIndex(query->getIndex());
                 rsp->setIndex(query->getIndex());
 
 
+                // Execute all callouts registered for packet6_send
+                if (HooksManager::getHooksManager().calloutsPresent(hook_index_pkt4_send_)) {
+                    CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+                    // Delete all previous arguments
+                    callout_handle->deleteAllArguments();
+
+                    // Clear skip flag if it was set in previous callouts
+                    callout_handle->setSkip(false);
+
+                    // Set our response
+                    callout_handle->setArgument("response4", rsp);
+
+                    // Call all installed callouts
+                    HooksManager::getHooksManager().callCallouts(hook_index_pkt4_send_,
+                                                    *callout_handle);
+
+                    // Callouts decided to skip the next processing step. The next
+                    // processing step would to send the packet, so skip at this
+                    // stage means "drop response".
+                    if (callout_handle->getSkip()) {
+                        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_HOOKS, DHCP4_HOOK_PACKET_SEND_SKIP);
+                        continue;
+                    }
+                }
+
                 LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA,
                 LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA,
                           DHCP4_RESPONSE_DATA)
                           DHCP4_RESPONSE_DATA)
                           .arg(rsp->getType()).arg(rsp->toText());
                           .arg(rsp->getType()).arg(rsp->toText());
 
 
                 if (rsp->pack()) {
                 if (rsp->pack()) {
                     try {
                     try {
-                        IfaceMgr::instance().send(rsp);
+                        sendPacket(rsp);
                     } catch (const std::exception& e) {
                     } catch (const std::exception& e) {
                         LOG_ERROR(dhcp4_logger, DHCP4_PACKET_SEND_FAIL).arg(e.what());
                         LOG_ERROR(dhcp4_logger, DHCP4_PACKET_SEND_FAIL).arg(e.what());
                     }
                     }
@@ -782,17 +876,48 @@ Dhcpv4Srv::serverReceivedPacketName(uint8_t type) {
 Subnet4Ptr
 Subnet4Ptr
 Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
 Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
 
 
+    Subnet4Ptr subnet;
     // Is this relayed message?
     // Is this relayed message?
     IOAddress relay = question->getGiaddr();
     IOAddress relay = question->getGiaddr();
     if (relay.toText() == "0.0.0.0") {
     if (relay.toText() == "0.0.0.0") {
 
 
         // Yes: Use relay address to select subnet
         // Yes: Use relay address to select subnet
-        return (CfgMgr::instance().getSubnet4(relay));
+        subnet = CfgMgr::instance().getSubnet4(relay);
     } else {
     } else {
 
 
         // No: Use client's address to select subnet
         // No: Use client's address to select subnet
-        return (CfgMgr::instance().getSubnet4(question->getRemoteAddr()));
+        subnet = CfgMgr::instance().getSubnet4(question->getRemoteAddr());
     }
     }
+
+    // Let's execute all callouts registered for packet_received
+    if (HooksManager::getHooksManager().calloutsPresent(hook_index_subnet4_select_)) {
+        CalloutHandlePtr callout_handle = getCalloutHandle(question);
+
+        // We're reusing callout_handle from previous calls
+        callout_handle->deleteAllArguments();
+
+        // Set new arguments
+        callout_handle->setArgument("query4", question);
+        callout_handle->setArgument("subnet4", subnet);
+        callout_handle->setArgument("subnet4collection", CfgMgr::instance().getSubnets4());
+
+        // Call user (and server-side) callouts
+        HooksManager::getHooksManager().callCallouts(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 severly limited
+        // (i.e. only global options will be assigned)
+        if (callout_handle->getSkip()) {
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_HOOKS, DHCP4_HOOK_SUBNET4_SELECT_SKIP);
+            return (Subnet4Ptr());
+        }
+
+        // Use whatever subnet was specified by the callout
+        callout_handle->getArgument("subnet4", subnet);
+    }
+
+    return (subnet);
 }
 }
 
 
 void
 void
@@ -820,5 +945,32 @@ Dhcpv4Srv::sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid) {
     }
     }
 }
 }
 
 
+isc::hooks::CalloutHandlePtr Dhcpv4Srv::getCalloutHandle(const Pkt4Ptr& pkt) {
+    // This method returns a CalloutHandle for a given packet. It is guaranteed
+    // to return the same callout_handle (so user library contexts are
+    // preserved). This method works well if the server processes one packet
+    // at a time. Once the server architecture is extended to cover parallel
+    // packets processing (e.g. delayed-ack, some form of buffering etc.), this
+    // method has to be extended (e.g. store callouts in a map and use pkt as
+    // a key). Additional code would be required to release the callout handle
+    // once the server finished processing.
+
+    CalloutHandlePtr callout_handle;
+    static Pkt4Ptr old_pointer;
+
+    if (!callout_handle ||
+        old_pointer != pkt) {
+        // This is the first packet or a different packet than previously
+        // passed to getCalloutHandle()
+
+        // Remember the pointer to this packet
+        old_pointer = pkt;
+
+        callout_handle = HooksManager::getHooksManager().createCalloutHandle();
+    }
+
+    return (callout_handle);
+}
+
 }   // namespace dhcp
 }   // namespace dhcp
 }   // namespace isc
 }   // namespace isc

+ 24 - 0
src/bin/dhcp4/dhcp4_srv.h

@@ -20,6 +20,7 @@
 #include <dhcp/option.h>
 #include <dhcp/option.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/alloc_engine.h>
 #include <dhcpsrv/alloc_engine.h>
+#include <hooks/callout_handle.h>
 
 
 #include <boost/noncopyable.hpp>
 #include <boost/noncopyable.hpp>
 
 
@@ -296,6 +297,18 @@ protected:
     /// initiate server shutdown procedure.
     /// initiate server shutdown procedure.
     volatile bool shutdown_;
     volatile bool shutdown_;
 
 
+    /// @brief dummy wrapper around IfaceMgr::receive4
+    ///
+    /// This method is useful for testing purposes, where its replacement
+    /// simulates reception of a packet. For that purpose it is protected.
+    virtual Pkt4Ptr receivePacket(int timeout);
+
+    /// @brief dummy wrapper around IfaceMgr::send()
+    ///
+    /// This method is useful for testing purposes, where its replacement
+    /// simulates transmission of a packet. For that purpose it is protected.
+    virtual void sendPacket(const Pkt4Ptr& pkt);
+
 private:
 private:
 
 
     /// @brief Constructs netmask option based on subnet4
     /// @brief Constructs netmask option based on subnet4
@@ -310,6 +323,17 @@ private:
     /// during normal operation (e.g. to use different allocators)
     /// during normal operation (e.g. to use different allocators)
     boost::shared_ptr<AllocEngine> alloc_engine_;
     boost::shared_ptr<AllocEngine> alloc_engine_;
 
 
+    /// @brief returns callout handle for specified packet
+    ///
+    /// @param pkt packet for which the handle should be returned
+    ///
+    /// @return a callout handle to be used in hooks related to said packet
+    isc::hooks::CalloutHandlePtr getCalloutHandle(const Pkt4Ptr& pkt);
+
+    /// Indexes for registered hook points
+    int hook_index_pkt4_receive_;
+    int hook_index_subnet4_select_;
+    int hook_index_pkt4_send_;
 };
 };
 
 
 }; // namespace isc::dhcp
 }; // namespace isc::dhcp

+ 9 - 1
src/lib/dhcpsrv/cfgmgr.h

@@ -201,6 +201,15 @@ public:
     /// completely new?
     /// completely new?
     void deleteSubnets6();
     void deleteSubnets6();
 
 
+    /// @brief returns const reference to all subnets6
+    ///
+    /// This is used in a hook (subnet4_select), where the hook is able
+    /// to choose a different subnet. Server code has to offer a list
+    /// of possible choices (i.e. all subnets).
+    /// @return a pointer to const Subnet6 collection
+    const Subnet4Collection* getSubnets4() {
+        return (&subnets4_);
+    }
 
 
     /// @brief returns const reference to all subnets6
     /// @brief returns const reference to all subnets6
     ///
     ///
@@ -212,7 +221,6 @@ public:
         return (&subnets6_);
         return (&subnets6_);
     }
     }
 
 
-
     /// @brief get IPv4 subnet by address
     /// @brief get IPv4 subnet by address
     ///
     ///
     /// Finds a matching subnet, based on an address. This can be used
     /// Finds a matching subnet, based on an address. This can be used