Kea 2.7.8
callout_manager.cc
Go to the documentation of this file.
1// Copyright (C) 2013-2024 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
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
22using namespace std;
23
24namespace isc {
25namespace hooks {
26
27// Constructor
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.
43void
44CalloutManager::checkLibraryIndex(int library_index) const {
45 if (((library_index >= -1) && (library_index <= num_libraries_)) ||
46 (library_index == INT_MAX)) {
47 return;
48 }
49
50 isc_throw(NoSuchLibrary, "library index " << library_index <<
51 " is not valid for the number of loaded libraries (" <<
52 num_libraries_ << ")");
53}
54
55// Register a callout for the current library.
56void
57CalloutManager::registerCallout(const std::string& name,
58 CalloutPtr callout,
59 int library_index) {
60 // Note the registration.
62 .arg(library_index).arg(name);
63
64 // Sanity check that the current library index is set to a valid value.
65 checkLibraryIndex(library_index);
66
67 // New hooks could have been registered since the manager was constructed.
68 ensureHookLibsVectorSize();
69
70 // Get the index associated with this hook (validating the name in the
71 // process).
72 int hook_index = server_hooks_.getIndex(name);
73
74 // Iterate through the callout vector for the hook from start to end,
75 // looking for the first entry where the library index is greater than
76 // the present index.
77 for (CalloutVector::iterator i = hook_vector_[hook_index].begin();
78 i != hook_vector_[hook_index].end(); ++i) {
79 if (i->first > library_index) {
80 // Found an element whose library index number is greater than the
81 // current index, so insert the new element ahead of this one.
82 hook_vector_[hook_index].insert(i, make_pair(library_index,
83 callout));
84 return;
85 }
86 }
87
88 // Reached the end of the vector, so there is no element in the (possibly
89 // empty) set of callouts with a library index greater than the current
90 // library index. Inset the callout at the end of the list.
91 hook_vector_[hook_index].push_back(make_pair(library_index, callout));
92}
93
94// Check if callouts are present for a given hook index.
95bool
96CalloutManager::calloutsPresent(int hook_index) const {
97 // Validate the hook index.
98 if ((hook_index < 0) ||
99 (static_cast<size_t>(hook_index) >= hook_vector_.size())) {
100 isc_throw(NoSuchHook, "hook index " << hook_index <<
101 " is not valid for the list of registered hooks");
102 }
103
104 // Valid, so are there any callouts associated with that hook?
105 return (!hook_vector_[hook_index].empty());
106}
107
108bool
109CalloutManager::commandHandlersPresent(const std::string& command_name) const {
110 // Check if the hook point for the specified command exists.
112 ServerHooks::commandToHookName(command_name));
113 if (index >= 0) {
114 // The hook point exits but it is possible that there are no
115 // callouts/command handlers. This is possible if there was a
116 // hook library supporting this command attached, but it was
117 // later unloaded. The hook points are not deregistered in
118 // this case. Only callouts are deregistered.
119 // Let's check if callouts are present for this hook point.
120 return (calloutsPresent(index));
121 }
122
123 // Hook point not created, so we don't support this command in
124 // any of the hooks libraries.
125 return (false);
126}
127
128// Call all the callouts for a given hook.
129void
130CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) {
131 // Clear the "skip" flag so we don't carry state from a previous call.
132 // This is done regardless of whether callouts are present to avoid passing
133 // any state from the previous call of callCallouts().
135
136 // Only initialize and iterate if there are callouts present. This check
137 // also catches the case of an invalid index.
138 if (calloutsPresent(hook_index)) {
139
140 // Set the current hook index. This is used should a callout wish to
141 // determine to what hook it is attached.
142 callout_handle.setCurrentHook(hook_index);
143
144 // This object will be used to measure execution time of each callout
145 // and the total time spent in callouts for this hook point.
146 util::Stopwatch stopwatch;
147
148 // Mark that the callouts begin for the hook.
150 .arg(server_hooks_.getName(callout_handle.getCurrentHook()));
151
152 // Call all the callouts.
153 for (auto const& i : hook_vector_[hook_index]) {
154 // In case the callout requires access to the context associated
155 // with the library, set the current library index to the index
156 // associated with the library that registered the callout being
157 // called.
158 callout_handle.setCurrentLibrary(i.first);
159
160 // Call the callout
161 try {
162 stopwatch.start();
163 int status = (*i.second)(callout_handle);
164 stopwatch.stop();
165 if (status == 0) {
168 .arg(callout_handle.getCurrentLibrary())
169 .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
170 .arg(PointerConverter(i.second).dlsymPtr())
171 .arg(stopwatch.logFormatLastDuration());
172 } else {
174 .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
175 .arg(callout_handle.getCurrentLibrary())
176 .arg(PointerConverter(i.second).dlsymPtr())
177 .arg(stopwatch.logFormatLastDuration());
178 }
179 } catch (const std::exception& e) {
180 // If an exception occurred, the stopwatch.stop() hasn't been
181 // called, so we have to call it here.
182 stopwatch.stop();
183 // Any exception, just ones based on isc::Exception
185 .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
186 .arg(callout_handle.getCurrentLibrary())
187 .arg(PointerConverter(i.second).dlsymPtr())
188 .arg(e.what())
189 .arg(stopwatch.logFormatLastDuration());
191 } catch (...) {
192 // If an exception occurred, the stopwatch.stop() hasn't been
193 // called, so we have to call it here.
194 stopwatch.stop();
195 // Any exception, not just ones based on isc::Exception
197 .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
198 .arg(callout_handle.getCurrentLibrary())
199 .arg(PointerConverter(i.second).dlsymPtr())
200 .arg("Unspecified exception")
201 .arg(stopwatch.logFormatLastDuration());
203 }
204 }
205
206 // Mark end of callout execution. Include the total execution
207 // time for callouts.
209 .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
210 .arg(stopwatch.logFormatTotalDuration());
211
212 // Reset the current hook and library indexes to an invalid value to
213 // catch any programming errors.
214 callout_handle.setCurrentHook(-1);
215 callout_handle.setCurrentLibrary(-1);
216 }
217}
218
219void
220CalloutManager::callCommandHandlers(const std::string& command_name,
221 CalloutHandle& callout_handle) {
222 // Get the index of the hook point for the specified command.
223 // This will throw an exception if the hook point doesn't exist.
224 // The caller should check if the hook point exists by calling
225 // commandHandlersPresent.
227 // Call the handlers for this command.
228 callCallouts(index, callout_handle);
229}
230
231// Deregister a callout registered by the current library on a particular hook.
232bool
233CalloutManager::deregisterCallout(const std::string& name, CalloutPtr callout,
234 int library_index) {
235 // Sanity check that the current library index is set to a valid value.
236 checkLibraryIndex(library_index);
237
238 // New hooks could have been registered since the manager was constructed.
239 ensureHookLibsVectorSize();
240
241 // Get the index associated with this hook (validating the name in the
242 // process).
243 int hook_index = server_hooks_.getIndex(name);
244
245 // New hooks can have been registered since the manager was constructed.
246 if (static_cast<size_t>(hook_index) >= hook_vector_.size()) {
247 return (false);
248 }
249
252 CalloutEntry target(library_index, callout);
253
257 size_t initial_size = hook_vector_[hook_index].size();
258
259 // The next bit is standard STL (see "Item 33" in "Effective STL" by
260 // Scott Meyers).
261 //
262 // remove_if reorders the hook vector so that all items not matching
263 // the predicate are at the start of the vector and returns a pointer
264 // to the next element. (In this case, the predicate is that the item
265 // is equal to the value of the passed callout.) The erase() call
266 // removes everything from that element to the end of the vector, i.e.
267 // all the matching elements.
268 hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
269 hook_vector_[hook_index].end(),
270 [&target] (CalloutEntry x) {
271 return (x == target); }),
272 hook_vector_[hook_index].end());
273
274 // Return an indication of whether anything was removed.
275 bool removed = initial_size != hook_vector_[hook_index].size();
276 if (removed) {
278 HOOKS_CALLOUT_DEREGISTERED).arg(library_index).arg(name);
279 }
280
281 return (removed);
282}
283
284// Deregister all callouts on a given hook.
285bool
287 int library_index) {
288 // New hooks could have been registered since the manager was constructed.
289 ensureHookLibsVectorSize();
290
291 // Get the index associated with this hook (validating the name in the
292 // process).
293 int hook_index = server_hooks_.getIndex(name);
294
297 CalloutEntry target(library_index, static_cast<CalloutPtr>(0));
298
302 size_t initial_size = hook_vector_[hook_index].size();
303
304 // Remove all callouts matching this library.
305 hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
306 hook_vector_[hook_index].end(),
307 [&target] (CalloutEntry x) {
308 return (x.first == target.first);
309 }),
310 hook_vector_[hook_index].end());
311
312 // Return an indication of whether anything was removed.
313 bool removed = initial_size != hook_vector_[hook_index].size();
314 if (removed) {
316 HOOKS_ALL_CALLOUTS_DEREGISTERED).arg(library_index).arg(name);
317 }
318
319 return (removed);
320}
321
322void
323CalloutManager::registerCommandHook(const std::string& command_name) {
324 // New hooks could have been registered since the manager was constructed.
325 ensureHookLibsVectorSize();
326
328 int hook_index = hooks.findIndex(ServerHooks::commandToHookName(command_name));
329 if (hook_index < 0) {
330 // Hook for this command doesn't exist. Let's create one.
331 hooks.registerHook(ServerHooks::commandToHookName(command_name));
332 // Callout Manager's vector of hooks have to be resized to hold the
333 // information about callouts for this new hook point. This should
334 // add new element at the end of the hook_vector_. The index of this
335 // element will match the index of the hook point in the ServerHooks
336 // because ServerHooks allocates indexes incrementally.
337 hook_vector_.resize(server_hooks_.getCount());
338 }
339}
340
341void
342CalloutManager::ensureHookLibsVectorSize() {
344 if (static_cast<size_t>(hooks.getCount()) > hook_vector_.size()) {
345 // Uh oh, there are more hook points that our vector allows.
346 hook_vector_.resize(hooks.getCount());
347 }
348}
349
350} // namespace util
351} // namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
Per-packet callout handle.
void setCurrentLibrary(int library_index)
Set current library index.
@ NEXT_STEP_CONTINUE
continue normally
@ NEXT_STEP_DROP
drop the packet
void setStatus(const CalloutNextStep next)
Sets the next processing step.
void setCurrentHook(int hook_index)
Set current hook index.
int getCurrentHook() const
Get current hook index.
int getCurrentLibrary() const
Get current library index.
void callCallouts(int hook_index, CalloutHandle &callout_handle)
Calls the callouts for a given hook.
bool commandHandlersPresent(const std::string &command_name) const
Checks if control command handlers are present for the specified command.
void callCommandHandlers(const std::string &command_name, CalloutHandle &callout_handle)
Calls the callouts/command handlers for a given command name.
CalloutManager(int num_libraries=0)
Constructor.
bool deregisterAllCallouts(const std::string &name, int library_index)
Removes all callouts on a hook for the current library.
bool deregisterCallout(const std::string &name, CalloutPtr callout, int library_index)
De-Register a callout on a hook for the current library.
bool calloutsPresent(int hook_index) const
Checks if callouts are present on a hook.
void registerCommandHook(const std::string &command_name)
Registers a hook point for the specified command name.
void registerCallout(const std::string &name, CalloutPtr callout, int library_index)
Register a callout on a hook for the current library.
Local class for conversion of void pointers to function pointers.
void * dlsymPtr() const
Return pointer returned by dlsym call.
Server hook collection.
int getIndex(const std::string &name) const
Get hook index.
static ServerHooks & getServerHooks()
Return ServerHooks object.
int findIndex(const std::string &name) const
Find hook index.
int getCount() const
Return number of hooks.
static std::string commandToHookName(const std::string &command_name)
Generates hook point name for the given control command name.
int registerHook(const std::string &name)
Register a hook.
std::string getName(int index) const
Get hook name.
Utility class to measure code execution times.
Definition stopwatch.h:35
void stop()
Stops the stopwatch.
Definition stopwatch.cc:34
void start()
Starts the stopwatch.
Definition stopwatch.cc:29
std::string logFormatTotalDuration() const
Returns the total measured duration in the format directly usable in the log messages.
Definition stopwatch.cc:79
std::string logFormatLastDuration() const
Returns the last measured duration in the format directly usable in log messages.
Definition stopwatch.cc:74
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition macros.h:32
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition macros.h:14
int(* CalloutPtr)(CalloutHandle &)
Typedef for a callout pointer. (Callouts must have "C" linkage.)
const int HOOKS_DBG_EXTENDED_CALLS
Definition hooks_log.h:29
const isc::log::MessageID HOOKS_CALLOUTS_COMPLETE
const isc::log::MessageID HOOKS_CALLOUT_EXCEPTION
const isc::log::MessageID HOOKS_CALLOUTS_BEGIN
const isc::log::MessageID HOOKS_CALLOUT_REGISTRATION
const int HOOKS_DBG_CALLS
Definition hooks_log.h:25
isc::log::Logger callouts_logger("callouts")
Callouts logger.
Definition hooks_log.h:44
const isc::log::MessageID HOOKS_ALL_CALLOUTS_DEREGISTERED
const isc::log::MessageID HOOKS_CALLOUT_ERROR
const isc::log::MessageID HOOKS_CALLOUT_DEREGISTERED
const isc::log::MessageID HOOKS_CALLOUT_CALLED
Defines the logger used by the top-level component of kea-lfc.