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