1 // Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC") 2 // 3 // This Source Code Form is subject to the terms of the Mozilla Public 4 // License, v. 2.0. If a copy of the MPL was not distributed with this 5 // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 7 #ifndef CALLOUT_MANAGER_H 8 #define CALLOUT_MANAGER_H 9 10 #include <exceptions/exceptions.h> 11 #include <hooks/library_handle.h> 12 #include <hooks/server_hooks.h> 13 14 #include <boost/shared_ptr.hpp> 15 16 #include <climits> 17 #include <map> 18 #include <string> 19 20 namespace isc { 21 namespace hooks { 22 23 /// @brief No such library 24 /// 25 /// Thrown if an attempt is made to set the current library index to a value 26 /// that is invalid for the number of loaded libraries. 27 class NoSuchLibrary : public Exception { 28 public: NoSuchLibrary(const char * file,size_t line,const char * what)29 NoSuchLibrary(const char* file, size_t line, const char* what) : 30 isc::Exception(file, line, what) {} 31 }; 32 33 /// @brief Callout Manager 34 /// 35 /// This class manages the registration, deregistration and execution of the 36 /// library callouts. It is part of the hooks framework used by the Kea 37 /// server, and is not for use by user-written code in a hooks library. 38 /// 39 /// In operation, the class needs to know two items of data: 40 /// 41 /// - The list of server hooks, which is used in two ways. Firstly, when a 42 /// library registers or deregisters a hook, it does so by name: the 43 /// @ref isc::hooks::ServerHooks object supplies the names of registered 44 /// hooks. Secondly, when the callouts associated with a hook are called by 45 /// the server, the server supplies the index of the relevant hook: this is 46 /// validated by reference to the list of hooks. 47 /// 48 /// - The number of loaded libraries. Each callout registered by a user 49 /// library is associated with that library, the callout manager storing both 50 /// a pointer to the callout and the index of the library in the list of 51 /// loaded libraries. When calling a callout, the callout manager maintains 52 /// the idea of a "current library index": this is used to access the context 53 /// associated with the library. 54 /// 55 /// These two items of data are supplied when an object of this class is 56 /// constructed. The latter (number of libraries) can be updated after the 57 /// class is constructed. (Such an update is used during library loading where 58 /// the CalloutManager has to be constructed before the libraries are loaded, 59 /// but one of the libraries subsequently fails to load.) 60 /// 61 /// The library index is important because it determines in what order callouts 62 /// on a particular hook are called. For each hook, the CalloutManager 63 /// maintains a vector of callouts ordered by library index. When a callout 64 /// is added to the list, it is added at the end of the callouts associated 65 /// with the current library. To clarify this further, suppose that three 66 /// libraries are loaded, A (assigned an index 1), B (assigned an index 2) and 67 /// C (assigned an index 3). Suppose A registers two callouts on a given hook, 68 /// A1 and A2 (in that order) B registers B1 and B2 (in that order) and C 69 /// registers C1 and C2 (in that order). Internally, the callouts are stored 70 /// in the order A1, A2, B1, B2, C1, and C2: this is also the order in which 71 /// they are called. 72 /// 73 /// Indexes range between 1 and n (where n is the number of the libraries 74 /// loaded) and are assigned to libraries based on the order the libraries 75 /// presented to the hooks framework for loading (something that occurs in the 76 /// isc::hooks::HooksManager) class. However, two other indexes are recognized, 77 /// 0 and INT_MAX. These are used when the server itself registers callouts - 78 /// the server is able to register callouts that get called before any 79 /// user-library callouts, and ones that get called after user-library callouts. 80 /// In other words, assuming the callouts on a hook are A1, A2, B1, B2, B3, C2, 81 /// C2 as before, and that the server registers S1 (to run before the 82 /// user-registered callouts) and S2 (to run after them), the callouts are 83 /// stored (and executed) in the order S1, A1, A2, B1, B2, B3, C2, C2, S2. In 84 /// summary, the recognized index values are: 85 /// 86 /// - < 0: invalid. 87 /// - 0: used for server-registered callouts that are called before 88 /// user-registered callouts. 89 /// - 1 - n: callouts from user libraries. 90 /// - INT_MAX: used for server-registered callouts called after 91 /// user-registered callouts. 92 /// 93 /// Since Kea 1.3.0 release hook libraries can register callouts as control 94 /// command handlers. Such handlers are associated with dynamically created 95 /// hook points which names are created after command names. For example, 96 /// if a command name is 'foo-bar', the name of the hook point to which 97 /// callouts/command handlers are registered is '$foo_bar'. Prefixing the 98 /// hook point name with the dollar sign eliminates potential conflicts 99 /// between hook points dedicated to commands handling and other (fixed) 100 /// hook points. 101 /// 102 /// Prefixing hook names for command handlers with a dollar sign precludes 103 /// auto registration of command handlers, i.e. hooks framework is unable 104 /// to match hook points with names of functions implementing command 105 /// handlers, because the dollar sign is not legal in C++ function names. 106 /// This is intended because we want hook libraries to explicitly register 107 /// commands handlers for supported commands and not rely on Kea to register 108 /// hook points for them. Should we find use cases for auto registration of 109 /// command handlers, we may modify the 110 /// @ref ServerHooks::commandToHookName to use an encoding of hook 111 /// point names for command handlers that would only contain characters 112 /// allowed in function names. 113 /// 114 /// The @ref CalloutManager::registerCommandHook has been added to allow for 115 /// dynamically creating hook points for which command handlers are registered. 116 /// This method is called from the @ref LibraryHandle::registerCommandCallout 117 /// as a result of registering the command handlers by the hook library in 118 /// its @c load() function. If the hook point for the given command already 119 /// exists, this function doesn't do anything. The 120 /// @ref LibraryHandle::registerCommandCallout can install callouts on this 121 /// hook point. 122 /// 123 /// Note that the callout functions do not access the CalloutManager: instead, 124 /// they use a LibraryHandle object. This contains an internal pointer to 125 /// the CalloutManager, but provides a restricted interface. In that way, 126 /// callouts are unable to affect callouts supplied by other libraries. 127 128 class CalloutManager { 129 private: 130 131 // Private typedefs 132 133 /// Element in the vector of callouts. The elements in the pair are the 134 /// index of the library from which this callout was registered, and a# 135 /// pointer to the callout itself. 136 typedef std::pair<int, CalloutPtr> CalloutEntry; 137 138 /// An element in the hook vector. Each element is a vector of callouts 139 /// associated with a given hook. 140 typedef std::vector<CalloutEntry> CalloutVector; 141 142 public: 143 144 /// @brief Constructor 145 /// 146 /// Initializes member variables, in particular sizing the hook vector 147 /// (the vector of callout vectors) to the appropriate size. 148 /// 149 /// @param num_libraries Number of loaded libraries. 150 /// 151 /// @throw isc::BadValue if the number of libraries is less than 0, 152 CalloutManager(int num_libraries = 0); 153 154 /// @brief Register a callout on a hook for the current library 155 /// 156 /// Registers a callout function for the current library with a given hook. 157 /// The callout is added to the end of the callouts for this library that 158 /// are associated with that hook. 159 /// 160 /// @param name Name of the hook to which the callout is added. 161 /// @param callout Pointer to the callout function to be registered. 162 /// @param library_index Library index used for registering the callout. 163 /// 164 /// @throw NoSuchHook The hook name is unrecognized. 165 /// @throw Unexpected The hook name is valid but an internal data structure 166 /// is of the wrong size. 167 void registerCallout(const std::string& name, 168 CalloutPtr callout, 169 int library_index); 170 171 /// @brief De-Register a callout on a hook for the current library 172 /// 173 /// Searches through the functions registered by the current library 174 /// with the named hook and removes all entries matching the 175 /// callout. 176 /// 177 /// @param name Name of the hook from which the callout is removed. 178 /// @param callout Pointer to the callout function to be removed. 179 /// @param library_index Library index used for deregistering the callout. 180 /// 181 /// @return true if a one or more callouts were deregistered. 182 /// 183 /// @throw NoSuchHook The hook name is unrecognized. 184 /// @throw Unexpected The hook name is valid but an internal data structure 185 /// is of the wrong size. 186 bool deregisterCallout(const std::string& name, 187 CalloutPtr callout, 188 int library_index); 189 190 /// @brief Removes all callouts on a hook for the current library 191 /// 192 /// Removes all callouts associated with a given hook that were registered 193 /// by the current library. 194 /// 195 /// @param name Name of the hook from which the callouts are removed. 196 /// @param library_index Library index used for deregistering all callouts. 197 /// 198 /// @return true if one or more callouts were deregistered. 199 /// 200 /// @throw NoSuchHook Thrown if the hook name is unrecognized. 201 bool deregisterAllCallouts(const std::string& name, int library_index); 202 203 /// @brief Checks if callouts are present on a hook 204 /// 205 /// Checks all loaded libraries and returns true if at least one callout 206 /// has been registered by any of them for the given hook. 207 /// 208 /// @param hook_index Hook index for which callouts are checked. 209 /// 210 /// @return true if callouts are present, false if not. 211 /// 212 /// @throw NoSuchHook Given index does not correspond to a valid hook. 213 bool calloutsPresent(int hook_index) const; 214 215 /// @brief Checks if control command handlers are present for the 216 /// specified command. 217 /// 218 /// @param command_name Command name for which handlers' presence should 219 /// be checked. 220 /// 221 /// @return true if there is a hook point associated with the specified 222 /// command and callouts/command handlers are installed for this hook 223 /// point, false otherwise. 224 bool commandHandlersPresent(const std::string& command_name) const; 225 226 /// @brief Calls the callouts for a given hook 227 /// 228 /// Iterates through the library handles and calls the callouts associated 229 /// with the given hook index. 230 /// 231 /// @note This method invalidates the current library index set with 232 /// setLibraryIndex(). 233 /// 234 /// @param hook_index Index of the hook to call. 235 /// @param callout_handle Reference to the CalloutHandle object for the 236 /// current object being processed. 237 void callCallouts(int hook_index, CalloutHandle& callout_handle); 238 239 /// @brief Calls the callouts/command handlers for a given command name. 240 /// 241 /// Iterates through the library handles and calls the command handlers 242 /// associated with the given command. It expects that the hook point 243 /// for this command exists (with a name being a command_name prefixed 244 /// with a dollar sign and with hyphens replaced with underscores). 245 /// 246 /// @param command_name Command name for which handlers should be called. 247 /// @param callout_handle Reference to the CalloutHandle object for the 248 /// current object being processed. 249 /// 250 /// @throw NoSuchHook if the hook point for the specified command does 251 /// not exist. 252 void callCommandHandlers(const std::string& command_name, 253 CalloutHandle& callout_handle); 254 255 /// @brief Registers a hook point for the specified command name. 256 /// 257 /// If the hook point for such command already exists, this function 258 /// doesn't do anything. The registered hook point name is created 259 /// after command_name by prefixing it with a dollar sign and replacing 260 /// all hyphens with underscores, e.g. for the 'foo-bar' command the 261 /// following hook point name will be generated: '$foo_bar'. 262 /// 263 /// @param command_name Command name for which the hook point should be 264 /// registered. 265 void registerCommandHook(const std::string& command_name); 266 267 /// @brief Get number of libraries 268 /// 269 /// Returns the number of libraries that this CalloutManager is expected 270 /// to serve. This is the number passed to its constructor. 271 /// 272 /// @return Number of libraries served by this CalloutManager. getNumLibraries()273 int getNumLibraries() const { 274 return (num_libraries_); 275 } 276 277 /// @brief Get current library index 278 /// 279 /// Returns the index of the "current" library. This the index associated 280 /// with the currently executing callout when callCallouts is executing. 281 /// When callCallouts() is not executing (as is the case when the load() 282 /// function in a user-library is called during the library load process), 283 /// the index can be set by setLibraryIndex(). 284 /// 285 /// @note The value set by this method is lost after a call to 286 /// callCallouts. 287 /// 288 /// @return Current library index. getLibraryIndex()289 int getLibraryIndex() const { 290 return (current_library_); 291 } 292 293 /// @brief Set current library index 294 /// 295 /// Sets the current library index. This has the following valid values: 296 /// 297 /// - -1: invalidate current index. 298 /// - 0: pre-user library callout. 299 /// - 1 - numlib: user-library callout (where "numlib" is the number of 300 /// libraries loaded in the system, this figure being passed to this 301 /// object at construction time). 302 /// - INT_MAX: post-user library callout. 303 /// 304 /// @param library_index New library index. 305 /// 306 /// @throw NoSuchLibrary if the index is not valid. setLibraryIndex(int library_index)307 void setLibraryIndex(int library_index) { 308 checkLibraryIndex(library_index); 309 current_library_ = library_index; 310 } 311 312 /// @defgroup calloutManagerLibraryHandles Callout manager library handles 313 /// 314 /// The CalloutManager offers three library handles: 315 /// 316 /// - a "standard" one, used to register and deregister callouts for 317 /// the library index that is marked as current in the CalloutManager. 318 /// When a callout is called, it is passed this one. 319 /// - a pre-library callout handle, used by the server to register 320 // callouts to run prior to user-library callouts. 321 /// - a post-library callout handle, used by the server to register 322 /// callouts to run after the user-library callouts. 323 //@{ 324 325 /// @brief Return library handle 326 /// 327 /// The library handle is available to the user callout via the callout 328 /// handle object. It provides a cut-down view of the CalloutManager, 329 /// allowing the callout to register and deregister callouts in the 330 /// library of which it is part, whilst denying access to anything that 331 /// may affect other libraries. 332 /// 333 /// @return Reference to library handle for this manager getLibraryHandle()334 LibraryHandle& getLibraryHandle() { 335 return (library_handle_); 336 } 337 338 /// @brief Return pre-user callouts library handle 339 /// 340 /// The LibraryHandle to affect callouts that will run before the 341 /// user-library callouts. 342 /// 343 /// @return Reference to pre-user library handle for this manager getPreLibraryHandle()344 LibraryHandle& getPreLibraryHandle() { 345 return (pre_library_handle_); 346 } 347 348 /// @brief Return post-user callouts library handle 349 /// 350 /// The LibraryHandle to affect callouts that will run before the 351 /// user-library callouts. 352 /// 353 /// @return Reference to post-user library handle for this manager getPostLibraryHandle()354 LibraryHandle& getPostLibraryHandle() { 355 return (post_library_handle_); 356 } 357 358 //@} 359 360 /// @brief Return number of currently available hooks getHookLibsVectorSize()361 size_t getHookLibsVectorSize() const { 362 return (hook_vector_.size()); 363 } 364 365 private: 366 367 /// @brief This method checks whether the hook_vector_ size is sufficient 368 /// and extends it if necessary. 369 /// 370 /// The problem is as follows: some hooks are initialized statically 371 /// from global static objects. ServerHooks object creates hooks_ collection 372 /// and CalloutManager creates its own hook_vector_ and both are initialized 373 /// to the same size. All works well so far. However, if some code at a 374 /// later time (e.g. a hook library) registers new hook point, then 375 /// ServerHooks::registerHook() will extend its hooks_ collection, but 376 /// the CalloutManager will keep the old hook_vector_ that is too small by 377 /// one. Now when the library is unloaded, deregisterAllCallouts will 378 /// go through all hook points and will eventually hit the one that 379 /// will return index greater than the hook_vector_ size. 380 /// 381 /// To solve this problem, ensureVectorSize was implemented. It should 382 /// be called (presumably from ServerHooks) every time a new hook point 383 /// is registered. It checks whether the vector size is sufficient and 384 /// extends it if necessary. It is safe to call it multiple times. It 385 /// may grow the vector size, but will never shrink it. 386 void ensureHookLibsVectorSize(); 387 388 /// @brief Check library index 389 /// 390 /// Ensures that the current library index is valid. This is called by 391 /// the hook registration functions. 392 /// 393 /// @param library_index Value to check for validity as a library index. 394 /// Valid values are 0 -> numlib + 1 and -1: see @ref setLibraryIndex 395 /// for the meaning of the various values. 396 /// 397 /// @throw NoSuchLibrary Library index is not valid. 398 void checkLibraryIndex(int library_index) const; 399 400 // Member variables 401 402 /// Reference to the singleton ServerHooks object. See the 403 /// @ref hooksmgMaintenanceGuide for information as to why the class holds 404 /// a reference instead of accessing the singleton within the code. 405 ServerHooks& server_hooks_; 406 407 /// Current library index. When a call is made to any of the callout 408 /// registration methods, this variable indicates the index of the user 409 /// library that should be associated with the call. 410 int current_library_; 411 412 /// Vector of callout vectors. There is one entry in this outer vector for 413 /// each hook. Each element is itself a vector, with one entry for each 414 /// callout registered for that hook. 415 std::vector<CalloutVector> hook_vector_; 416 417 /// LibraryHandle object user by the callout to access the callout 418 /// registration methods on this CalloutManager object. The object is set 419 /// such that the index of the library associated with any operation is 420 /// whatever is currently set in the CalloutManager. 421 LibraryHandle library_handle_; 422 423 /// LibraryHandle for callouts to be registered as being called before 424 /// the user-registered callouts. 425 LibraryHandle pre_library_handle_; 426 427 /// LibraryHandle for callouts to be registered as being called after 428 /// the user-registered callouts. 429 LibraryHandle post_library_handle_; 430 431 /// Number of libraries. 432 int num_libraries_; 433 }; 434 435 } // namespace util 436 } // namespace isc 437 438 #endif // CALLOUT_MANAGER_H 439