// 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 #include #include #include #include #include #include #include #include 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 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 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& manager, const boost::shared_ptr& lmcoll = boost::shared_ptr()); /// @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 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 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(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 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(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 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 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(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 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(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 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 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 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 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