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