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 #include <config.h>
8 
9 #include <hooks/callout_handle.h>
10 #include <hooks/callout_manager.h>
11 #include <hooks/hooks_log.h>
12 #include <hooks/pointer_converter.h>
13 #include <util/stopwatch.h>
14 
15 #include <boost/static_assert.hpp>
16 
17 #include <algorithm>
18 #include <climits>
19 #include <functional>
20 #include <utility>
21 
22 using namespace std;
23 
24 namespace isc {
25 namespace hooks {
26 
27 // Constructor
CalloutManager(int num_libraries)28 CalloutManager::CalloutManager(int num_libraries)
29     : server_hooks_(ServerHooks::getServerHooks()), current_library_(-1),
30       hook_vector_(ServerHooks::getServerHooks().getCount()),
31       library_handle_(*this), pre_library_handle_(*this, 0),
32       post_library_handle_(*this, INT_MAX), num_libraries_(num_libraries) {
33     if (num_libraries < 0) {
34         isc_throw(isc::BadValue, "number of libraries passed to the "
35                   "CalloutManager must be >= 0");
36     }
37 }
38 
39 // Check that the index of a library is valid.  It can range from 1 - n
40 // (n is the number of libraries), 0 (pre-user library callouts), or INT_MAX
41 // (post-user library callouts).  It can also be -1 to indicate an invalid
42 // value.
43 
44 void
checkLibraryIndex(int library_index) const45 CalloutManager::checkLibraryIndex(int library_index) const {
46     if (((library_index >= -1) && (library_index <= num_libraries_)) ||
47         (library_index == INT_MAX)) {
48         return;
49     }
50 
51     isc_throw(NoSuchLibrary, "library index " << library_index <<
52               " is not valid for the number of loaded libraries (" <<
53               num_libraries_ << ")");
54 }
55 
56 // Register a callout for the current library.
57 
58 void
registerCallout(const std::string & name,CalloutPtr callout,int library_index)59 CalloutManager::registerCallout(const std::string& name,
60                                 CalloutPtr callout,
61                                 int library_index) {
62     // Note the registration.
63     LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUT_REGISTRATION)
64         .arg(library_index).arg(name);
65 
66     // Sanity check that the current library index is set to a valid value.
67     checkLibraryIndex(library_index);
68 
69     // New hooks could have been registered since the manager was constructed.
70     ensureHookLibsVectorSize();
71 
72     // Get the index associated with this hook (validating the name in the
73     // process).
74     int hook_index = server_hooks_.getIndex(name);
75 
76     // Iterate through the callout vector for the hook from start to end,
77     // looking for the first entry where the library index is greater than
78     // the present index.
79     for (CalloutVector::iterator i = hook_vector_[hook_index].begin();
80          i != hook_vector_[hook_index].end(); ++i) {
81         if (i->first > library_index) {
82             // Found an element whose library index number is greater than the
83             // current index, so insert the new element ahead of this one.
84             hook_vector_[hook_index].insert(i, make_pair(library_index,
85                                                          callout));
86             return;
87         }
88     }
89 
90     // Reached the end of the vector, so there is no element in the (possibly
91     // empty) set of callouts with a library index greater than the current
92     // library index.  Inset the callout at the end of the list.
93     hook_vector_[hook_index].push_back(make_pair(library_index, callout));
94 }
95 
96 // Check if callouts are present for a given hook index.
97 
98 bool
calloutsPresent(int hook_index) const99 CalloutManager::calloutsPresent(int hook_index) const {
100     // Validate the hook index.
101     if ((hook_index < 0) || (hook_index >= hook_vector_.size())) {
102         isc_throw(NoSuchHook, "hook index " << hook_index <<
103                   " is not valid for the list of registered hooks");
104     }
105 
106     // Valid, so are there any callouts associated with that hook?
107     return (!hook_vector_[hook_index].empty());
108 }
109 
110 bool
commandHandlersPresent(const std::string & command_name) const111 CalloutManager::commandHandlersPresent(const std::string& command_name) const {
112     // Check if the hook point for the specified command exists.
113     int index = ServerHooks::getServerHooks().findIndex(
114                     ServerHooks::commandToHookName(command_name));
115     if (index >= 0) {
116         // The hook point exits but it is possible that there are no
117         // callouts/command handlers. This is possible if there was a
118         // hook library supporting this command attached, but it was
119         // later unloaded. The hook points are not deregistered in
120         // this case. Only callouts are deregistered.
121         // Let's check if callouts are present for this hook point.
122         return (calloutsPresent(index));
123     }
124 
125     // Hook point not created, so we don't support this command in
126     // any of the hooks libraries.
127     return (false);
128 }
129 
130 
131 // Call all the callouts for a given hook.
132 
133 void
callCallouts(int hook_index,CalloutHandle & callout_handle)134 CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) {
135     // Clear the "skip" flag so we don't carry state from a previous call.
136     // This is done regardless of whether callouts are present to avoid passing
137     // any state from the previous call of callCallouts().
138     callout_handle.setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
139 
140     // Only initialize and iterate if there are callouts present.  This check
141     // also catches the case of an invalid index.
142     if (calloutsPresent(hook_index)) {
143 
144         // Set the current hook index.  This is used should a callout wish to
145         // determine to what hook it is attached.
146         callout_handle.setCurrentHook(hook_index);
147 
148         // This object will be used to measure execution time of each callout
149         // and the total time spent in callouts for this hook point.
150         util::Stopwatch stopwatch;
151 
152         // Mark that the callouts begin for the hook.
153         LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_BEGIN)
154             .arg(server_hooks_.getName(callout_handle.getCurrentHook()));
155 
156         // Call all the callouts.
157         for (CalloutVector::const_iterator i = hook_vector_[hook_index].begin();
158              i != hook_vector_[hook_index].end(); ++i) {
159             // In case the callout requires access to the context associated
160             // with the library, set the current library index to the index
161             // associated with the library that registered the callout being
162             // called.
163             callout_handle.setCurrentLibrary(i->first);
164 
165             // Call the callout
166             try {
167                 stopwatch.start();
168                 int status = (*i->second)(callout_handle);
169                 stopwatch.stop();
170                 if (status == 0) {
171                     LOG_DEBUG(callouts_logger, HOOKS_DBG_EXTENDED_CALLS,
172                               HOOKS_CALLOUT_CALLED)
173                         .arg(callout_handle.getCurrentLibrary())
174                         .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
175                         .arg(PointerConverter(i->second).dlsymPtr())
176                         .arg(stopwatch.logFormatLastDuration());
177                 } else {
178                     LOG_ERROR(callouts_logger, HOOKS_CALLOUT_ERROR)
179                         .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
180                         .arg(callout_handle.getCurrentLibrary())
181                         .arg(PointerConverter(i->second).dlsymPtr())
182                         .arg(stopwatch.logFormatLastDuration());
183                 }
184             } catch (const std::exception& e) {
185                 // If an exception occurred, the stopwatch.stop() hasn't been
186                 // called, so we have to call it here.
187                 stopwatch.stop();
188                 // Any exception, not just ones based on isc::Exception
189                 LOG_ERROR(callouts_logger, HOOKS_CALLOUT_EXCEPTION)
190                     .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
191                     .arg(callout_handle.getCurrentLibrary())
192                     .arg(PointerConverter(i->second).dlsymPtr())
193                     .arg(e.what())
194                     .arg(stopwatch.logFormatLastDuration());
195             }
196 
197         }
198 
199         // Mark end of callout execution. Include the total execution
200         // time for callouts.
201         LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_COMPLETE)
202             .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
203             .arg(stopwatch.logFormatTotalDuration());
204 
205         // Reset the current hook and library indexes to an invalid value to
206         // catch any programming errors.
207         callout_handle.setCurrentHook(-1);
208         callout_handle.setCurrentLibrary(-1);
209     }
210 }
211 
212 void
callCommandHandlers(const std::string & command_name,CalloutHandle & callout_handle)213 CalloutManager::callCommandHandlers(const std::string& command_name,
214                                     CalloutHandle& callout_handle) {
215     // Get the index of the hook point for the specified command.
216     // This will throw an exception if the hook point doesn't exist.
217     // The caller should check if the hook point exists by calling
218     // commandHandlersPresent.
219     int index = ServerHooks::getServerHooks().getIndex(
220                     ServerHooks::commandToHookName(command_name));
221     // Call the handlers for this command.
222     callCallouts(index, callout_handle);
223 }
224 
225 
226 // Deregister a callout registered by the current library on a particular hook.
227 
228 bool
deregisterCallout(const std::string & name,CalloutPtr callout,int library_index)229 CalloutManager::deregisterCallout(const std::string& name, CalloutPtr callout,
230                                   int library_index) {
231     // Sanity check that the current library index is set to a valid value.
232     checkLibraryIndex(library_index);
233 
234     // New hooks could have been registered since the manager was constructed.
235     ensureHookLibsVectorSize();
236 
237     // Get the index associated with this hook (validating the name in the
238     // process).
239     int hook_index = server_hooks_.getIndex(name);
240 
241     // New hooks can have been registered since the manager was constructed.
242     if (hook_index >= hook_vector_.size()) {
243         return (false);
244     }
245 
246     /// Construct a CalloutEntry matching the current library and the callout
247     /// we want to remove.
248     CalloutEntry target(library_index, callout);
249 
250     /// To decide if any entries were removed, we'll record the initial size
251     /// of the callout vector for the hook, and compare it with the size after
252     /// the removal.
253     size_t initial_size = hook_vector_[hook_index].size();
254 
255     // The next bit is standard STL (see "Item 33" in "Effective STL" by
256     // Scott Meyers).
257     //
258     // remove_if reorders the hook vector so that all items not matching
259     // the predicate are at the start of the vector and returns a pointer
260     // to the next element. (In this case, the predicate is that the item
261     // is equal to the value of the passed callout.)  The erase() call
262     // removes everything from that element to the end of the vector, i.e.
263     // all the matching elements.
264     hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
265                                              hook_vector_[hook_index].end(),
266                                              [&target] (CalloutEntry x) {
267                                                  return (x == target); }),
268                                    hook_vector_[hook_index].end());
269 
270     // Return an indication of whether anything was removed.
271     bool removed = initial_size != hook_vector_[hook_index].size();
272     if (removed) {
273         LOG_DEBUG(callouts_logger, HOOKS_DBG_EXTENDED_CALLS,
274                   HOOKS_CALLOUT_DEREGISTERED).arg(library_index).arg(name);
275     }
276 
277     return (removed);
278 }
279 
280 // Deregister all callouts on a given hook.
281 
282 bool
deregisterAllCallouts(const std::string & name,int library_index)283 CalloutManager::deregisterAllCallouts(const std::string& name,
284                                       int library_index) {
285     // New hooks could have been registered since the manager was constructed.
286     ensureHookLibsVectorSize();
287 
288     // Get the index associated with this hook (validating the name in the
289     // process).
290     int hook_index = server_hooks_.getIndex(name);
291 
292     /// Construct a CalloutEntry matching the current library (the callout
293     /// pointer is NULL as we are not checking that).
294     CalloutEntry target(library_index, static_cast<CalloutPtr>(0));
295 
296     /// To decide if any entries were removed, we'll record the initial size
297     /// of the callout vector for the hook, and compare it with the size after
298     /// the removal.
299     size_t initial_size = hook_vector_[hook_index].size();
300 
301     // Remove all callouts matching this library.
302     hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
303                                              hook_vector_[hook_index].end(),
304                                              [&target] (CalloutEntry x) {
305                                                  return (x.first == target.first);
306                                              }),
307                                    hook_vector_[hook_index].end());
308 
309     // Return an indication of whether anything was removed.
310     bool removed = initial_size != hook_vector_[hook_index].size();
311     if (removed) {
312         LOG_DEBUG(callouts_logger, HOOKS_DBG_EXTENDED_CALLS,
313                   HOOKS_ALL_CALLOUTS_DEREGISTERED).arg(library_index).arg(name);
314     }
315 
316     return (removed);
317 }
318 
319 void
registerCommandHook(const std::string & command_name)320 CalloutManager::registerCommandHook(const std::string& command_name) {
321     // New hooks could have been registered since the manager was constructed.
322     ensureHookLibsVectorSize();
323 
324     ServerHooks& hooks = ServerHooks::getServerHooks();
325     int hook_index = hooks.findIndex(ServerHooks::commandToHookName(command_name));
326     if (hook_index < 0) {
327         // Hook for this command doesn't exist. Let's create one.
328         hooks.registerHook(ServerHooks::commandToHookName(command_name));
329         // Callout Manager's vector of hooks have to be resized to hold the
330         // information about callouts for this new hook point. This should
331         // add new element at the end of the hook_vector_. The index of this
332         // element will match the index of the hook point in the ServerHooks
333         // because ServerHooks allocates indexes incrementally.
334         hook_vector_.resize(server_hooks_.getCount());
335     }
336 }
337 
338 void
ensureHookLibsVectorSize()339 CalloutManager::ensureHookLibsVectorSize() {
340     ServerHooks& hooks = ServerHooks::getServerHooks();
341     if (hooks.getCount() > hook_vector_.size()) {
342         // Uh oh, there are more hook points that our vector allows.
343         hook_vector_.resize(hooks.getCount());
344     }
345 }
346 
347 } // namespace util
348 } // namespace isc
349