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