Kea 2.7.5
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) || (hook_index >= hook_vector_.size())) {
99 isc_throw(NoSuchHook, "hook index " << hook_index <<
100 " is not valid for the list of registered hooks");
101 }
102
103 // Valid, so are there any callouts associated with that hook?
104 return (!hook_vector_[hook_index].empty());
105}
106
107bool
108CalloutManager::commandHandlersPresent(const std::string& command_name) const {
109 // Check if the hook point for the specified command exists.
110 int index = ServerHooks::getServerHooks().findIndex(
111 ServerHooks::commandToHookName(command_name));
112 if (index >= 0) {
113 // The hook point exits but it is possible that there are no
114 // callouts/command handlers. This is possible if there was a
115 // hook library supporting this command attached, but it was
116 // later unloaded. The hook points are not deregistered in
117 // this case. Only callouts are deregistered.
118 // Let's check if callouts are present for this hook point.
119 return (calloutsPresent(index));
120 }
121
122 // Hook point not created, so we don't support this command in
123 // any of the hooks libraries.
124 return (false);
125}
126
127// Call all the callouts for a given hook.
128void
129CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) {
130 // Clear the "skip" flag so we don't carry state from a previous call.
131 // This is done regardless of whether callouts are present to avoid passing
132 // any state from the previous call of callCallouts().
133 callout_handle.setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
134
135 // Only initialize and iterate if there are callouts present. This check
136 // also catches the case of an invalid index.
137 if (calloutsPresent(hook_index)) {
138
139 // Set the current hook index. This is used should a callout wish to
140 // determine to what hook it is attached.
141 callout_handle.setCurrentHook(hook_index);
142
143 // This object will be used to measure execution time of each callout
144 // and the total time spent in callouts for this hook point.
145 util::Stopwatch stopwatch;
146
147 // Mark that the callouts begin for the hook.
149 .arg(server_hooks_.getName(callout_handle.getCurrentHook()));
150
151 // Call all the callouts.
152 for (auto const& i : hook_vector_[hook_index]) {
153 // In case the callout requires access to the context associated
154 // with the library, set the current library index to the index
155 // associated with the library that registered the callout being
156 // called.
157 callout_handle.setCurrentLibrary(i.first);
158
159 // Call the callout
160 try {
161 stopwatch.start();
162 int status = (*i.second)(callout_handle);
163 stopwatch.stop();
164 if (status == 0) {
167 .arg(callout_handle.getCurrentLibrary())
168 .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
169 .arg(PointerConverter(i.second).dlsymPtr())
170 .arg(stopwatch.logFormatLastDuration());
171 } else {
173 .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
174 .arg(callout_handle.getCurrentLibrary())
175 .arg(PointerConverter(i.second).dlsymPtr())
176 .arg(stopwatch.logFormatLastDuration());
177 }
178 } catch (const std::exception& e) {
179 // If an exception occurred, the stopwatch.stop() hasn't been
180 // called, so we have to call it here.
181 stopwatch.stop();
182 // Any exception, just ones based on isc::Exception
184 .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
185 .arg(callout_handle.getCurrentLibrary())
186 .arg(PointerConverter(i.second).dlsymPtr())
187 .arg(e.what())
188 .arg(stopwatch.logFormatLastDuration());
189 callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
190 } catch (...) {
191 // If an exception occurred, the stopwatch.stop() hasn't been
192 // called, so we have to call it here.
193 stopwatch.stop();
194 // Any exception, not just ones based on isc::Exception
196 .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
197 .arg(callout_handle.getCurrentLibrary())
198 .arg(PointerConverter(i.second).dlsymPtr())
199 .arg("Unspecified exception")
200 .arg(stopwatch.logFormatLastDuration());
201 callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
202 }
203 }
204
205 // Mark end of callout execution. Include the total execution
206 // time for callouts.
208 .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
209 .arg(stopwatch.logFormatTotalDuration());
210
211 // Reset the current hook and library indexes to an invalid value to
212 // catch any programming errors.
213 callout_handle.setCurrentHook(-1);
214 callout_handle.setCurrentLibrary(-1);
215 }
216}
217
218void
219CalloutManager::callCommandHandlers(const std::string& command_name,
220 CalloutHandle& callout_handle) {
221 // Get the index of the hook point for the specified command.
222 // This will throw an exception if the hook point doesn't exist.
223 // The caller should check if the hook point exists by calling
224 // commandHandlersPresent.
225 int index = ServerHooks::getServerHooks().getIndex(ServerHooks::commandToHookName(command_name));
226 // Call the handlers for this command.
227 callCallouts(index, callout_handle);
228}
229
230// Deregister a callout registered by the current library on a particular hook.
231bool
232CalloutManager::deregisterCallout(const std::string& name, CalloutPtr callout,
233 int library_index) {
234 // Sanity check that the current library index is set to a valid value.
235 checkLibraryIndex(library_index);
236
237 // New hooks could have been registered since the manager was constructed.
238 ensureHookLibsVectorSize();
239
240 // Get the index associated with this hook (validating the name in the
241 // process).
242 int hook_index = server_hooks_.getIndex(name);
243
244 // New hooks can have been registered since the manager was constructed.
245 if (hook_index >= hook_vector_.size()) {
246 return (false);
247 }
248
251 CalloutEntry target(library_index, callout);
252
256 size_t initial_size = hook_vector_[hook_index].size();
257
258 // The next bit is standard STL (see "Item 33" in "Effective STL" by
259 // Scott Meyers).
260 //
261 // remove_if reorders the hook vector so that all items not matching
262 // the predicate are at the start of the vector and returns a pointer
263 // to the next element. (In this case, the predicate is that the item
264 // is equal to the value of the passed callout.) The erase() call
265 // removes everything from that element to the end of the vector, i.e.
266 // all the matching elements.
267 hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
268 hook_vector_[hook_index].end(),
269 [&target] (CalloutEntry x) {
270 return (x == target); }),
271 hook_vector_[hook_index].end());
272
273 // Return an indication of whether anything was removed.
274 bool removed = initial_size != hook_vector_[hook_index].size();
275 if (removed) {
277 HOOKS_CALLOUT_DEREGISTERED).arg(library_index).arg(name);
278 }
279
280 return (removed);
281}
282
283// Deregister all callouts on a given hook.
284bool
286 int library_index) {
287 // New hooks could have been registered since the manager was constructed.
288 ensureHookLibsVectorSize();
289
290 // Get the index associated with this hook (validating the name in the
291 // process).
292 int hook_index = server_hooks_.getIndex(name);
293
296 CalloutEntry target(library_index, static_cast<CalloutPtr>(0));
297
301 size_t initial_size = hook_vector_[hook_index].size();
302
303 // Remove all callouts matching this library.
304 hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
305 hook_vector_[hook_index].end(),
306 [&target] (CalloutEntry x) {
307 return (x.first == target.first);
308 }),
309 hook_vector_[hook_index].end());
310
311 // Return an indication of whether anything was removed.
312 bool removed = initial_size != hook_vector_[hook_index].size();
313 if (removed) {
315 HOOKS_ALL_CALLOUTS_DEREGISTERED).arg(library_index).arg(name);
316 }
317
318 return (removed);
319}
320
321void
322CalloutManager::registerCommandHook(const std::string& command_name) {
323 // New hooks could have been registered since the manager was constructed.
324 ensureHookLibsVectorSize();
325
327 int hook_index = hooks.findIndex(ServerHooks::commandToHookName(command_name));
328 if (hook_index < 0) {
329 // Hook for this command doesn't exist. Let's create one.
330 hooks.registerHook(ServerHooks::commandToHookName(command_name));
331 // Callout Manager's vector of hooks have to be resized to hold the
332 // information about callouts for this new hook point. This should
333 // add new element at the end of the hook_vector_. The index of this
334 // element will match the index of the hook point in the ServerHooks
335 // because ServerHooks allocates indexes incrementally.
336 hook_vector_.resize(server_hooks_.getCount());
337 }
338}
339
340void
341CalloutManager::ensureHookLibsVectorSize() {
343 if (hooks.getCount() > hook_vector_.size()) {
344 // Uh oh, there are more hook points that our vector allows.
345 hook_vector_.resize(hooks.getCount());
346 }
347}
348
349} // namespace util
350} // 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.
@ NEXT_STEP_CONTINUE
continue normally
@ NEXT_STEP_DROP
drop the packet
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
#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.