Kea  2.1.6-git
callout_manager.cc
Go to the documentation of this file.
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>
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
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
45 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
59 CalloutManager::registerCallout(const std::string& name,
60  CalloutPtr callout,
61  int library_index) {
62  // Note the 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
99 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
111 CalloutManager::commandHandlersPresent(const std::string& command_name) const {
112  // Check if the hook point for the specified command exists.
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
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().
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.
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) {
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 {
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
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.
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
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.
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
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 
248  CalloutEntry target(library_index, callout);
249 
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) {
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
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 
294  CalloutEntry target(library_index, static_cast<CalloutPtr>(0));
295 
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) {
313  HOOKS_ALL_CALLOUTS_DEREGISTERED).arg(library_index).arg(name);
314  }
315 
316  return (removed);
317 }
318 
319 void
320 CalloutManager::registerCommandHook(const std::string& command_name) {
321  // New hooks could have been registered since the manager was constructed.
322  ensureHookLibsVectorSize();
323 
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
339 CalloutManager::ensureHookLibsVectorSize() {
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
void setStatus(const CalloutNextStep next)
Sets the next processing step.
const int HOOKS_DBG_EXTENDED_CALLS
Definition: hooks_log.h:29
int(* CalloutPtr)(CalloutHandle &)
Typedef for a callout pointer. (Callouts must have "C" linkage.)
void registerCommandHook(const std::string &command_name)
Registers a hook point for the specified command name.
int findIndex(const std::string &name) const
Find hook index.
void registerCallout(const std::string &name, CalloutPtr callout, int library_index)
Register a callout on a hook for the current library.
void callCallouts(int hook_index, CalloutHandle &callout_handle)
Calls the callouts for a given hook.
int getCurrentHook() const
Get current hook index.
static ServerHooks & getServerHooks()
Return ServerHooks object.
bool commandHandlersPresent(const std::string &command_name) const
Checks if control command handlers are present for the specified command.
int getCount() const
Return number of hooks.
Definition: server_hooks.h:133
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition: macros.h:32
STL namespace.
std::string getName(int index) const
Get hook name.
const isc::log::MessageID HOOKS_CALLOUTS_COMPLETE
void * dlsymPtr() const
Return pointer returned by dlsym call.
Local class for conversion of void pointers to function pointers.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
void stop()
Stops the stopwatch.
Definition: stopwatch.cc:35
const isc::log::MessageID HOOKS_CALLOUT_REGISTRATION
isc::log::Logger callouts_logger("callouts")
Callouts logger.
Definition: hooks_log.h:44
Per-packet callout handle.
bool calloutsPresent(int hook_index) const
Checks if callouts are present on a hook.
Utility class to measure code execution times.
Definition: stopwatch.h:35
const isc::log::MessageID HOOKS_CALLOUT_ERROR
const isc::log::MessageID HOOKS_CALLOUT_CALLED
std::string logFormatLastDuration() const
Returns the last measured duration in the format directly usable in log messages. ...
Definition: stopwatch.cc:75
void setCurrentHook(int hook_index)
Set current hook index.
const int HOOKS_DBG_CALLS
Definition: hooks_log.h:25
int getCurrentLibrary() const
Get current library index.
Defines the logger used by the top-level component of kea-lfc.
bool deregisterAllCallouts(const std::string &name, int library_index)
Removes all callouts on a hook for the current library.
Invalid hook.
Definition: server_hooks.h:36
static std::string commandToHookName(const std::string &command_name)
Generates hook point name for the given control command name.
const isc::log::MessageID HOOKS_CALLOUT_EXCEPTION
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
void setCurrentLibrary(int library_index)
Set current library index.
const isc::log::MessageID HOOKS_CALLOUT_DEREGISTERED
const isc::log::MessageID HOOKS_CALLOUTS_BEGIN
int getIndex(const std::string &name) const
Get hook index.
const isc::log::MessageID HOOKS_ALL_CALLOUTS_DEREGISTERED
Server hook collection.
Definition: server_hooks.h:62
std::string logFormatTotalDuration() const
Returns the total measured duration in the format directly usable in the log messages.
Definition: stopwatch.cc:80
bool deregisterCallout(const std::string &name, CalloutPtr callout, int library_index)
De-Register a callout on a hook for the current library.
void callCommandHandlers(const std::string &command_name, CalloutHandle &callout_handle)
Calls the callouts/command handlers for a given command name.
int registerHook(const std::string &name)
Register a hook.
Definition: server_hooks.cc:44
void start()
Starts the stopwatch.
Definition: stopwatch.cc:30