1 // Copyright (C) 2013-2020 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_HANDLE_H 8 #define CALLOUT_HANDLE_H 9 10 #include <exceptions/exceptions.h> 11 #include <hooks/library_handle.h> 12 #include <hooks/parking_lots.h> 13 14 #include <boost/any.hpp> 15 #include <boost/shared_ptr.hpp> 16 17 #include <map> 18 #include <string> 19 #include <vector> 20 21 namespace isc { 22 namespace hooks { 23 24 class ServerHooks; 25 26 /// @brief No such argument 27 /// 28 /// Thrown if an attempt is made access an argument that does not exist. 29 30 class NoSuchArgument : public Exception { 31 public: NoSuchArgument(const char * file,size_t line,const char * what)32 NoSuchArgument(const char* file, size_t line, const char* what) : 33 isc::Exception(file, line, what) {} 34 }; 35 36 /// @brief No such callout context item 37 /// 38 /// Thrown if an attempt is made to get an item of data from this callout's 39 /// context and either the context or an item in the context with that name 40 /// does not exist. 41 42 class NoSuchCalloutContext : public Exception { 43 public: NoSuchCalloutContext(const char * file,size_t line,const char * what)44 NoSuchCalloutContext(const char* file, size_t line, const char* what) : 45 isc::Exception(file, line, what) {} 46 }; 47 48 // Forward declaration of the library handle and related collection classes. 49 50 class CalloutManager; 51 class LibraryManagerCollection; 52 53 /// @brief Per-packet callout handle 54 /// 55 /// An object of this class is associated with every packet (or request) 56 /// processed by the server. It forms the principle means of passing data 57 /// between the server and the user-library callouts. 58 /// 59 /// The class allows access to the following information: 60 /// 61 /// - Arguments. When the callouts associated with a hook are called, they 62 /// are passed information by the server (and can return information to it) 63 /// through name/value pairs. Each of these pairs is an argument and the 64 /// information is accessed through the {get,set}Argument() methods. 65 /// 66 /// - Per-packet context. Each packet has a context associated with it, this 67 /// context being on a per-library basis. In other words, As a packet passes 68 /// through the callouts associated with a given library, the callouts can 69 /// associate and retrieve information with the packet. The per-library 70 /// nature of the context means that the callouts within a given library can 71 /// pass packet-specific information between one another, but they cannot pass 72 /// information to callous within another library. Typically such context 73 /// is created in the "context_create" callout and destroyed in the 74 /// "context_destroy" callout. The information is accessed through the 75 /// {get,set}Context() methods. 76 77 class CalloutHandle { 78 public: 79 80 /// @brief Specifies allowed next steps 81 /// 82 /// Those values are used to designate the next step in packet processing. 83 /// They are set by hook callouts and read by the Kea server. See 84 /// @ref setStatus for detailed description of each value. 85 enum CalloutNextStep { 86 NEXT_STEP_CONTINUE = 0, ///< continue normally 87 NEXT_STEP_SKIP = 1, ///< skip the next processing step 88 NEXT_STEP_DROP = 2, ///< drop the packet 89 NEXT_STEP_PARK = 3 ///< park the packet 90 }; 91 92 93 /// Typedef to allow abbreviation of iterator specification in methods. 94 /// The std::string is the argument name and the "boost::any" is the 95 /// corresponding value associated with it. 96 typedef std::map<std::string, boost::any> ElementCollection; 97 98 /// Typedef to allow abbreviations in specifications when accessing 99 /// context. The ElementCollection is the name/value collection for 100 /// a particular context. The "int" corresponds to the index of an 101 /// associated library - there is a 1:1 correspondence between libraries 102 /// and a name.value collection. 103 /// 104 /// The collection of contexts is stored in a map, as not every library 105 /// will require creation of a context associated with each packet. In 106 /// addition, the structure is more flexible in that the size does not 107 /// need to be set when the CalloutHandle is constructed. 108 typedef std::map<int, ElementCollection> ContextCollection; 109 110 /// @brief Constructor 111 /// 112 /// Creates the object and calls the callouts on the "context_create" 113 /// hook. 114 /// 115 /// Of the two arguments passed, only the pointer to the callout manager is 116 /// actively used. The second argument, the pointer to the library manager 117 /// collection, is used for lifetime control: after use, the callout handle 118 /// may contain pointers to memory allocated by the loaded libraries. The 119 /// used of a shared pointer to the collection of library managers means 120 /// that the libraries that could have allocated memory in a callout handle 121 /// will not be unloaded until all such handles have been destroyed. This 122 /// issue is discussed in more detail in the documentation for 123 /// isc::hooks::LibraryManager. 124 /// 125 /// @param manager Pointer to the callout manager object. 126 /// @param lmcoll Pointer to the library manager collection. This has a 127 /// null default for testing purposes. 128 CalloutHandle(const boost::shared_ptr<CalloutManager>& manager, 129 const boost::shared_ptr<LibraryManagerCollection>& lmcoll = 130 boost::shared_ptr<LibraryManagerCollection>()); 131 132 /// @brief Destructor 133 /// 134 /// Calls the context_destroy callback to release any per-packet context. 135 /// It also clears stored data to avoid problems during member destruction. 136 ~CalloutHandle(); 137 138 /// @brief Set argument 139 /// 140 /// Sets the value of an argument. The argument is created if it does not 141 /// already exist. 142 /// 143 /// @param name Name of the argument. 144 /// @param value Value to set. That can be of any data type. 145 template <typename T> setArgument(const std::string & name,T value)146 void setArgument(const std::string& name, T value) { 147 arguments_[name] = value; 148 } 149 150 /// @brief Get argument 151 /// 152 /// Gets the value of an argument. 153 /// 154 /// @param name Name of the element in the argument list to get. 155 /// @param value [out] Value to set. The type of "value" is important: 156 /// it must match the type of the value set. 157 /// 158 /// @throw NoSuchArgument No argument with the given name is present. 159 /// @throw boost::bad_any_cast An argument with the given name is present, 160 /// but the data type of the value is not the same as the type of 161 /// the variable provided to receive the value. 162 template <typename T> getArgument(const std::string & name,T & value)163 void getArgument(const std::string& name, T& value) const { 164 ElementCollection::const_iterator element_ptr = arguments_.find(name); 165 if (element_ptr == arguments_.end()) { 166 isc_throw(NoSuchArgument, "unable to find argument with name " << 167 name); 168 } 169 170 value = boost::any_cast<T>(element_ptr->second); 171 } 172 173 /// @brief Get argument names 174 /// 175 /// Returns a vector holding the names of arguments in the argument 176 /// vector. 177 /// 178 /// @return Vector of strings reflecting argument names. 179 std::vector<std::string> getArgumentNames() const; 180 181 /// @brief Delete argument 182 /// 183 /// Deletes an argument of the given name. If an argument of that name 184 /// does not exist, the method is a no-op. 185 /// 186 /// N.B. If the element is a raw pointer, the pointed-to data is NOT deleted 187 /// by this method. 188 /// 189 /// @param name Name of the element in the argument list to set. deleteArgument(const std::string & name)190 void deleteArgument(const std::string& name) { 191 static_cast<void>(arguments_.erase(name)); 192 } 193 194 /// @brief Delete all arguments 195 /// 196 /// Deletes all arguments associated with this context. 197 /// 198 /// N.B. If any elements are raw pointers, the pointed-to data is NOT 199 /// deleted by this method. deleteAllArguments()200 void deleteAllArguments() { 201 arguments_.clear(); 202 } 203 204 /// @brief Sets the next processing step. 205 /// 206 /// This method is used by the callouts to determine the next step 207 /// in processing. This method replaces former setSkip() method 208 /// that allowed only two values. 209 /// 210 /// Currently there are three possible value allowed: 211 /// NEXT_STEP_CONTINUE - tells the server to continue processing as usual 212 /// (equivalent of previous setSkip(false) ) 213 /// 214 /// NEXT_STEP_SKIP - tells the server to skip the processing. Exact meaning 215 /// is hook specific. See hook documentation for details. 216 /// (equivalent of previous setSkip(true)) 217 /// 218 /// NEXT_STEP_DROP - tells the server to unconditionally drop the packet 219 /// and do not process it further. 220 /// 221 /// NEXT_STEP_PARK - tells the server to "park" the packet. The packet will 222 /// wait in the queue for being unparked, e.g. as a result 223 /// of completion of the asynchronous performed by the 224 /// hooks library operation. 225 /// 226 /// This variable is interrogated by the server to see if the remaining 227 /// callouts associated with the current hook should be bypassed. 228 /// 229 /// @param next New value of the next step status. setStatus(const CalloutNextStep next)230 void setStatus(const CalloutNextStep next) { 231 next_step_ = next; 232 } 233 234 /// @brief Returns the next processing step. 235 /// 236 /// Gets the current value of the next step. See @ref setStatus for detailed 237 /// definition. 238 /// 239 /// @return Current value of the skip flag. getStatus()240 CalloutNextStep getStatus() const { 241 return (next_step_); 242 } 243 244 /// @brief Set context 245 /// 246 /// Sets an element in the context associated with the current library. If 247 /// an element of the name is already present, it is replaced. 248 /// 249 /// @param name Name of the element in the context to set. 250 /// @param value Value to set. 251 template <typename T> setContext(const std::string & name,T value)252 void setContext(const std::string& name, T value) { 253 getContextForLibrary()[name] = value; 254 } 255 256 /// @brief Get context 257 /// 258 /// Gets an element from the context associated with the current library. 259 /// 260 /// @param name Name of the element in the context to get. 261 /// @param value [out] Value to set. The type of "value" is important: 262 /// it must match the type of the value set. 263 /// 264 /// @throw NoSuchCalloutContext Thrown if no context element with the name 265 /// "name" is present. 266 /// @throw boost::bad_any_cast Thrown if the context element is present 267 /// but the type of the data is not the same as the type of the 268 /// variable provided to receive its value. 269 template <typename T> getContext(const std::string & name,T & value)270 void getContext(const std::string& name, T& value) const { 271 const ElementCollection& lib_context = getContextForLibrary(); 272 273 ElementCollection::const_iterator element_ptr = lib_context.find(name); 274 if (element_ptr == lib_context.end()) { 275 isc_throw(NoSuchCalloutContext, "unable to find callout context " 276 "item " << name << " in the context associated with " 277 "current library"); 278 } 279 280 value = boost::any_cast<T>(element_ptr->second); 281 } 282 283 /// @brief Get context names 284 /// 285 /// Returns a vector holding the names of items in the context associated 286 /// with the current library. 287 /// 288 /// @return Vector of strings reflecting the names of items in the callout 289 /// context associated with the current library. 290 std::vector<std::string> getContextNames() const; 291 292 /// @brief Delete context element 293 /// 294 /// Deletes an item of the given name from the context associated with the 295 /// current library. If an item of that name does not exist, the method is 296 /// a no-op. 297 /// 298 /// N.B. If the element is a raw pointer, the pointed-to data is NOT deleted 299 /// by this. 300 /// 301 /// @param name Name of the context item to delete. deleteContext(const std::string & name)302 void deleteContext(const std::string& name) { 303 static_cast<void>(getContextForLibrary().erase(name)); 304 } 305 306 /// @brief Delete all context items 307 /// 308 /// Deletes all items from the context associated with the current library. 309 /// 310 /// N.B. If any elements are raw pointers, the pointed-to data is NOT 311 /// deleted by this. deleteAllContext()312 void deleteAllContext() { 313 getContextForLibrary().clear(); 314 } 315 316 /// @brief Get hook name 317 /// 318 /// Get the name of the hook to which the current callout is attached. 319 /// This can be the null string if the CalloutHandle is being accessed 320 /// outside of the CalloutManager's "callCallouts" method. 321 /// 322 /// @return Name of the current hook or the empty string if none. 323 std::string getHookName() const; 324 325 /// @brief Returns pointer to the parking lot handle for this hook point. 326 /// 327 /// @return pointer to the parking lot handle 328 ParkingLotHandlePtr getParkingLotHandlePtr() const; 329 330 /// @brief Get current library index 331 /// 332 /// @return The current library index getCurrentLibrary()333 int getCurrentLibrary() const { 334 return (current_library_); 335 } 336 337 /// @brief Set current library index 338 /// 339 /// @param library_index The library index setCurrentLibrary(int library_index)340 void setCurrentLibrary(int library_index) { 341 current_library_ = library_index; 342 } 343 344 /// @brief Get current hook index 345 /// 346 /// @return The current hook index getCurrentHook()347 int getCurrentHook() const { 348 return (current_hook_); 349 } 350 351 /// @brief Set current hook index 352 /// 353 /// @param hook_index The hook index setCurrentHook(int hook_index)354 void setCurrentHook(int hook_index) { 355 current_hook_ = hook_index; 356 } 357 358 private: 359 360 /// @brief Check index 361 /// 362 /// Gets the current library index, throwing an exception if it is not set 363 /// or is invalid for the current library collection. 364 /// 365 /// @return Current library index, valid for this library collection. 366 /// 367 /// @throw InvalidIndex current library index is not valid for the library 368 /// handle collection. 369 int getLibraryIndex() const; 370 371 /// @brief Return reference to context for current library 372 /// 373 /// Called by all context-setting functions, this returns a reference to 374 /// the callout context for the current library, creating a context if it 375 /// does not exist. 376 /// 377 /// @return Reference to the collection of name/value pairs associated 378 /// with the current library. 379 /// 380 /// @throw InvalidIndex current library index is not valid for the library 381 /// handle collection. 382 ElementCollection& getContextForLibrary(); 383 384 /// @brief Return reference to context for current library (const version) 385 /// 386 /// Called by all context-accessing functions, this a reference to the 387 /// callout context for the current library. An exception is thrown if 388 /// it does not exist. 389 /// 390 /// @return Reference to the collection of name/value pairs associated 391 /// with the current library. 392 /// 393 /// @throw NoSuchCalloutContext Thrown if there is no ElementCollection 394 /// associated with the current library. 395 const ElementCollection& getContextForLibrary() const; 396 397 // Member variables 398 399 /// Pointer to the collection of libraries for which this handle has been 400 /// created. 401 boost::shared_ptr<LibraryManagerCollection> lm_collection_; 402 403 /// Collection of arguments passed to the callouts 404 ElementCollection arguments_; 405 406 /// Context collection - there is one entry per library context. 407 ContextCollection context_collection_; 408 409 /// Callout manager. 410 boost::shared_ptr<CalloutManager> manager_; 411 412 /// Reference to the singleton ServerHooks object. See the 413 /// @ref hooksmgMaintenanceGuide for information as to why the class holds 414 /// a reference instead of accessing the singleton within the code. 415 ServerHooks& server_hooks_; 416 417 /// @brief Current library. 418 /// 419 /// When a call is made to @ref CalloutManager::callCallouts, this holds 420 /// the index of the current library. It is set to an invalid value (-1) 421 /// otherwise. 422 int current_library_; 423 424 /// @brief Current hook. 425 /// 426 /// When a call is made to @ref CalloutManager::callCallouts, this holds 427 /// the index of the current hook. It is set to an invalid value (-1) 428 /// otherwise. 429 int current_hook_; 430 431 /// Next processing step, indicating what the server should do next. 432 CalloutNextStep next_step_; 433 }; 434 435 /// A shared pointer to a CalloutHandle object. 436 typedef boost::shared_ptr<CalloutHandle> CalloutHandlePtr; 437 438 /// @brief Wrapper class around callout handle which automatically 439 /// resets handle's state. 440 /// 441 /// The Kea servers often require to associate processed packets with 442 /// @c CalloutHandle instances. This is to facilitate the case when the 443 /// hooks library passes information between the callouts using the 444 /// 'context' stored in the callout handle. The callouts invoked throughout 445 /// the packet lifetime have access to the context information for the 446 /// given packet. 447 /// 448 /// The association between the packets and the callout handles is 449 /// achieved by giving the ownership of the @c CalloutHandle objects to 450 /// the @c Pkt objects. When the @c Pkt object goes out of scope, it should 451 /// also release the pointer to the owned @c CalloutHandle object. 452 /// However, this causes a risk of circular dependency between the shared 453 /// pointer to the @c Pkt object and the shared pointer to the 454 /// @c CalloutHandle it owns, because the pointer to the packet is often 455 /// set as an argument of the callout handle prior to invoking a callout. 456 /// 457 /// In order to break the circular dependency, the arguments of the 458 /// callout handle must be deleted as soon as they are not needed 459 /// anymore. This class is a wrapper around the callout handle object, 460 /// which resets its state during construction and destruction. All 461 /// Kea hook points must use this class within the scope where the 462 /// @c HooksManager::callCallouts is invoked to reset the state of the 463 /// callout handle. The state is reset when this object goes out of 464 /// scope. 465 /// 466 /// Currently, the following operations are performed during the reset: 467 /// - all arguments of the callout handle are deleted, 468 /// - the next step status is set to @c CalloutHandle::NEXT_STEP CONTINUE 469 /// 470 /// This class must never be modified to also delete the context 471 /// information from the callout handle. The context is intended 472 /// to be used to share stateful data across callouts and hook points 473 /// and its contents must exist for the duration of the packet lifecycle. 474 /// Otherwise, we could simply re-create the callout handle for 475 /// each hook point and we wouldn't need this RAII class. 476 class ScopedCalloutHandleState { 477 public: 478 479 /// @brief Constructor. 480 /// 481 /// Resets state of the callout handle. 482 /// 483 /// @param callout_handle reference to the pointer to the callout 484 /// handle which state should be reset. 485 /// @throw isc::BadValue if the callout handle is null. 486 explicit ScopedCalloutHandleState(const CalloutHandlePtr& callout_handle); 487 488 /// @brief Destructor. 489 /// 490 /// Resets state of the callout handle. 491 ~ScopedCalloutHandleState(); 492 493 private: 494 495 /// @brief Resets the callout handle state. 496 /// 497 /// It is used internally by the constructor and destructor. 498 void resetState(); 499 500 /// @brief Holds pointer to the wrapped callout handle. 501 CalloutHandlePtr callout_handle_; 502 }; 503 504 } // namespace hooks 505 } // namespace isc 506 507 508 #endif // CALLOUT_HANDLE_H 509