diff options
Diffstat (limited to 'src/lib/hooks/callout_handle.h')
-rw-r--r-- | src/lib/hooks/callout_handle.h | 512 |
1 files changed, 512 insertions, 0 deletions
diff --git a/src/lib/hooks/callout_handle.h b/src/lib/hooks/callout_handle.h new file mode 100644 index 0000000..fb4b30a --- /dev/null +++ b/src/lib/hooks/callout_handle.h @@ -0,0 +1,512 @@ +// Copyright (C) 2013-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef CALLOUT_HANDLE_H +#define CALLOUT_HANDLE_H + +#include <exceptions/exceptions.h> +#include <hooks/library_handle.h> +#include <hooks/parking_lots.h> +#include <util/dhcp_space.h> + +#include <boost/any.hpp> +#include <boost/shared_ptr.hpp> + +#include <map> +#include <string> +#include <vector> + +namespace isc { +namespace hooks { + +class ServerHooks; + +/// @brief No such argument +/// +/// Thrown if an attempt is made access an argument that does not exist. + +class NoSuchArgument : public Exception { +public: + NoSuchArgument(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// @brief No such callout context item +/// +/// Thrown if an attempt is made to get an item of data from this callout's +/// context and either the context or an item in the context with that name +/// does not exist. + +class NoSuchCalloutContext : public Exception { +public: + NoSuchCalloutContext(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +// Forward declaration of the library handle and related collection classes. + +class CalloutManager; +class LibraryManagerCollection; + +/// @brief Per-packet callout handle +/// +/// An object of this class is associated with every packet (or request) +/// processed by the server. It forms the principle means of passing data +/// between the server and the user-library callouts. +/// +/// The class allows access to the following information: +/// +/// - Arguments. When the callouts associated with a hook are called, they +/// are passed information by the server (and can return information to it) +/// through name/value pairs. Each of these pairs is an argument and the +/// information is accessed through the {get,set}Argument() methods. +/// +/// - Per-packet context. Each packet has a context associated with it, this +/// context being on a per-library basis. In other words, As a packet passes +/// through the callouts associated with a given library, the callouts can +/// associate and retrieve information with the packet. The per-library +/// nature of the context means that the callouts within a given library can +/// pass packet-specific information between one another, but they cannot pass +/// information to callous within another library. Typically such context +/// is created in the "context_create" callout and destroyed in the +/// "context_destroy" callout. The information is accessed through the +/// {get,set}Context() methods. + +class CalloutHandle { +public: + + /// @brief Specifies allowed next steps + /// + /// Those values are used to designate the next step in packet processing. + /// They are set by hook callouts and read by the Kea server. See + /// @ref setStatus for detailed description of each value. + enum CalloutNextStep { + NEXT_STEP_CONTINUE = 0, ///< continue normally + NEXT_STEP_SKIP = 1, ///< skip the next processing step + NEXT_STEP_DROP = 2, ///< drop the packet + NEXT_STEP_PARK = 3 ///< park the packet + }; + + + /// Typedef to allow abbreviation of iterator specification in methods. + /// The std::string is the argument name and the "boost::any" is the + /// corresponding value associated with it. + typedef std::map<std::string, boost::any> ElementCollection; + + /// Typedef to allow abbreviations in specifications when accessing + /// context. The ElementCollection is the name/value collection for + /// a particular context. The "int" corresponds to the index of an + /// associated library - there is a 1:1 correspondence between libraries + /// and a name.value collection. + /// + /// The collection of contexts is stored in a map, as not every library + /// will require creation of a context associated with each packet. In + /// addition, the structure is more flexible in that the size does not + /// need to be set when the CalloutHandle is constructed. + typedef std::map<int, ElementCollection> ContextCollection; + + /// @brief Constructor + /// + /// Creates the object and calls the callouts on the "context_create" + /// hook. + /// + /// Of the two arguments passed, only the pointer to the callout manager is + /// actively used. The second argument, the pointer to the library manager + /// collection, is used for lifetime control: after use, the callout handle + /// may contain pointers to memory allocated by the loaded libraries. The + /// used of a shared pointer to the collection of library managers means + /// that the libraries that could have allocated memory in a callout handle + /// will not be unloaded until all such handles have been destroyed. This + /// issue is discussed in more detail in the documentation for + /// isc::hooks::LibraryManager. + /// + /// @param manager Pointer to the callout manager object. + /// @param lmcoll Pointer to the library manager collection. This has a + /// null default for testing purposes. + CalloutHandle(const boost::shared_ptr<CalloutManager>& manager, + const boost::shared_ptr<LibraryManagerCollection>& lmcoll = + boost::shared_ptr<LibraryManagerCollection>()); + + /// @brief Destructor + /// + /// Calls the context_destroy callback to release any per-packet context. + /// It also clears stored data to avoid problems during member destruction. + ~CalloutHandle(); + + /// @brief Set argument + /// + /// Sets the value of an argument. The argument is created if it does not + /// already exist. + /// + /// @param name Name of the argument. + /// @param value Value to set. That can be of any data type. + template <typename T> + void setArgument(const std::string& name, T value) { + arguments_[name] = value; + } + + /// @brief Get argument + /// + /// Gets the value of an argument. + /// + /// @param name Name of the element in the argument list to get. + /// @param value [out] Value to set. The type of "value" is important: + /// it must match the type of the value set. + /// + /// @throw NoSuchArgument No argument with the given name is present. + /// @throw boost::bad_any_cast An argument with the given name is present, + /// but the data type of the value is not the same as the type of + /// the variable provided to receive the value. + template <typename T> + void getArgument(const std::string& name, T& value) const { + ElementCollection::const_iterator element_ptr = arguments_.find(name); + if (element_ptr == arguments_.end()) { + isc_throw(NoSuchArgument, "unable to find argument with name " << + name); + } + + value = boost::any_cast<T>(element_ptr->second); + } + + /// @brief Get argument names + /// + /// Returns a vector holding the names of arguments in the argument + /// vector. + /// + /// @return Vector of strings reflecting argument names. + std::vector<std::string> getArgumentNames() const; + + /// @brief Delete argument + /// + /// Deletes an argument of the given name. If an argument of that name + /// does not exist, the method is a no-op. + /// + /// N.B. If the element is a raw pointer, the pointed-to data is NOT deleted + /// by this method. + /// + /// @param name Name of the element in the argument list to set. + void deleteArgument(const std::string& name) { + static_cast<void>(arguments_.erase(name)); + } + + /// @brief Delete all arguments + /// + /// Deletes all arguments associated with this context. + /// + /// N.B. If any elements are raw pointers, the pointed-to data is NOT + /// deleted by this method. + void deleteAllArguments() { + arguments_.clear(); + } + + /// @brief Sets the next processing step. + /// + /// This method is used by the callouts to determine the next step + /// in processing. This method replaces former setSkip() method + /// that allowed only two values. + /// + /// Currently there are three possible value allowed: + /// NEXT_STEP_CONTINUE - tells the server to continue processing as usual + /// (equivalent of previous setSkip(false) ) + /// + /// NEXT_STEP_SKIP - tells the server to skip the processing. Exact meaning + /// is hook specific. See hook documentation for details. + /// (equivalent of previous setSkip(true)) + /// + /// NEXT_STEP_DROP - tells the server to unconditionally drop the packet + /// and do not process it further. + /// + /// NEXT_STEP_PARK - tells the server to "park" the packet. The packet will + /// wait in the queue for being unparked, e.g. as a result + /// of completion of the asynchronous performed by the + /// hooks library operation. + /// + /// This variable is interrogated by the server to see if the remaining + /// callouts associated with the current hook should be bypassed. + /// + /// @param next New value of the next step status. + void setStatus(const CalloutNextStep next) { + next_step_ = next; + } + + /// @brief Returns the next processing step. + /// + /// Gets the current value of the next step. See @ref setStatus for detailed + /// definition. + /// + /// @return Current value of the skip flag. + CalloutNextStep getStatus() const { + return (next_step_); + } + + /// @brief Set context + /// + /// Sets an element in the context associated with the current library. If + /// an element of the name is already present, it is replaced. + /// + /// @param name Name of the element in the context to set. + /// @param value Value to set. + template <typename T> + void setContext(const std::string& name, T value) { + getContextForLibrary()[name] = value; + } + + /// @brief Get context + /// + /// Gets an element from the context associated with the current library. + /// + /// @param name Name of the element in the context to get. + /// @param value [out] Value to set. The type of "value" is important: + /// it must match the type of the value set. + /// + /// @throw NoSuchCalloutContext Thrown if no context element with the name + /// "name" is present. + /// @throw boost::bad_any_cast Thrown if the context element is present + /// but the type of the data is not the same as the type of the + /// variable provided to receive its value. + template <typename T> + void getContext(const std::string& name, T& value) const { + const ElementCollection& lib_context = getContextForLibrary(); + + ElementCollection::const_iterator element_ptr = lib_context.find(name); + if (element_ptr == lib_context.end()) { + isc_throw(NoSuchCalloutContext, "unable to find callout context " + "item " << name << " in the context associated with " + "current library"); + } + + value = boost::any_cast<T>(element_ptr->second); + } + + /// @brief Get context names + /// + /// Returns a vector holding the names of items in the context associated + /// with the current library. + /// + /// @return Vector of strings reflecting the names of items in the callout + /// context associated with the current library. + std::vector<std::string> getContextNames() const; + + /// @brief Delete context element + /// + /// Deletes an item of the given name from the context associated with the + /// current library. If an item of that name does not exist, the method is + /// a no-op. + /// + /// N.B. If the element is a raw pointer, the pointed-to data is NOT deleted + /// by this. + /// + /// @param name Name of the context item to delete. + void deleteContext(const std::string& name) { + static_cast<void>(getContextForLibrary().erase(name)); + } + + /// @brief Delete all context items + /// + /// Deletes all items from the context associated with the current library. + /// + /// N.B. If any elements are raw pointers, the pointed-to data is NOT + /// deleted by this. + void deleteAllContext() { + getContextForLibrary().clear(); + } + + /// @brief Get hook name + /// + /// Get the name of the hook to which the current callout is attached. + /// This can be the null string if the CalloutHandle is being accessed + /// outside of the CalloutManager's "callCallouts" method. + /// + /// @return Name of the current hook or the empty string if none. + std::string getHookName() const; + + /// @brief Returns pointer to the parking lot handle for this hook point. + /// + /// @return pointer to the parking lot handle + ParkingLotHandlePtr getParkingLotHandlePtr() const; + + /// @brief Get current library index + /// + /// @return The current library index + int getCurrentLibrary() const { + return (current_library_); + } + + /// @brief Set current library index + /// + /// @param library_index The library index + void setCurrentLibrary(int library_index) { + current_library_ = library_index; + } + + /// @brief Get current hook index + /// + /// @return The current hook index + int getCurrentHook() const { + return (current_hook_); + } + + /// @brief Set current hook index + /// + /// @param hook_index The hook index + void setCurrentHook(int hook_index) { + current_hook_ = hook_index; + } + +private: + + /// @brief Check index + /// + /// Gets the current library index, throwing an exception if it is not set + /// or is invalid for the current library collection. + /// + /// @return Current library index, valid for this library collection. + /// + /// @throw InvalidIndex current library index is not valid for the library + /// handle collection. + int getLibraryIndex() const; + + /// @brief Return reference to context for current library + /// + /// Called by all context-setting functions, this returns a reference to + /// the callout context for the current library, creating a context if it + /// does not exist. + /// + /// @return Reference to the collection of name/value pairs associated + /// with the current library. + /// + /// @throw InvalidIndex current library index is not valid for the library + /// handle collection. + ElementCollection& getContextForLibrary(); + + /// @brief Return reference to context for current library (const version) + /// + /// Called by all context-accessing functions, this a reference to the + /// callout context for the current library. An exception is thrown if + /// it does not exist. + /// + /// @return Reference to the collection of name/value pairs associated + /// with the current library. + /// + /// @throw NoSuchCalloutContext Thrown if there is no ElementCollection + /// associated with the current library. + const ElementCollection& getContextForLibrary() const; + + // Member variables + + /// Pointer to the collection of libraries for which this handle has been + /// created. + boost::shared_ptr<LibraryManagerCollection> lm_collection_; + + /// Collection of arguments passed to the callouts + ElementCollection arguments_; + + /// Context collection - there is one entry per library context. + ContextCollection context_collection_; + + /// Callout manager. + boost::shared_ptr<CalloutManager> manager_; + + /// Reference to the singleton ServerHooks object. See the + /// @ref hooksmgMaintenanceGuide for information as to why the class holds + /// a reference instead of accessing the singleton within the code. + ServerHooks& server_hooks_; + + /// @brief Current library. + /// + /// When a call is made to @ref CalloutManager::callCallouts, this holds + /// the index of the current library. It is set to an invalid value (-1) + /// otherwise. + int current_library_; + + /// @brief Current hook. + /// + /// When a call is made to @ref CalloutManager::callCallouts, this holds + /// the index of the current hook. It is set to an invalid value (-1) + /// otherwise. + int current_hook_; + + /// Next processing step, indicating what the server should do next. + CalloutNextStep next_step_; +}; + +/// A shared pointer to a CalloutHandle object. +typedef boost::shared_ptr<CalloutHandle> CalloutHandlePtr; + +/// @brief Wrapper class around callout handle which automatically +/// resets handle's state. +/// +/// The Kea servers often require to associate processed packets with +/// @c CalloutHandle instances. This is to facilitate the case when the +/// hooks library passes information between the callouts using the +/// 'context' stored in the callout handle. The callouts invoked throughout +/// the packet lifetime have access to the context information for the +/// given packet. +/// +/// The association between the packets and the callout handles is +/// achieved by giving the ownership of the @c CalloutHandle objects to +/// the @c Pkt objects. When the @c Pkt object goes out of scope, it should +/// also release the pointer to the owned @c CalloutHandle object. +/// However, this causes a risk of circular dependency between the shared +/// pointer to the @c Pkt object and the shared pointer to the +/// @c CalloutHandle it owns, because the pointer to the packet is often +/// set as an argument of the callout handle prior to invoking a callout. +/// +/// In order to break the circular dependency, the arguments of the +/// callout handle must be deleted as soon as they are not needed +/// anymore. This class is a wrapper around the callout handle object, +/// which resets its state during construction and destruction. All +/// Kea hook points must use this class within the scope where the +/// @c HooksManager::callCallouts is invoked to reset the state of the +/// callout handle. The state is reset when this object goes out of +/// scope. +/// +/// Currently, the following operations are performed during the reset: +/// - all arguments of the callout handle are deleted, +/// - the next step status is set to @c CalloutHandle::NEXT_STEP CONTINUE +/// +/// This class must never be modified to also delete the context +/// information from the callout handle. The context is intended +/// to be used to share stateful data across callouts and hook points +/// and its contents must exist for the duration of the packet lifecycle. +/// Otherwise, we could simply re-create the callout handle for +/// each hook point and we wouldn't need this RAII class. +class ScopedCalloutHandleState { +public: + + /// @brief Constructor. + /// + /// Resets state of the callout handle. + /// + /// @param callout_handle reference to the pointer to the callout + /// handle which state should be reset. + /// @throw isc::BadValue if the callout handle is null. + explicit ScopedCalloutHandleState(const CalloutHandlePtr& callout_handle); + + /// @brief Destructor. + /// + /// Resets state of the callout handle. + ~ScopedCalloutHandleState(); + + /// @brief Continuation callback. + std::function<void()> on_completion_; + +private: + + /// @brief Resets the callout handle state. + /// + /// It is used internally by the constructor and destructor. + void resetState(); + + /// @brief Holds pointer to the wrapped callout handle. + CalloutHandlePtr callout_handle_; +}; + +} // namespace hooks +} // namespace isc + + +#endif // CALLOUT_HANDLE_H |