Coverage Report

Created: 2026-02-14 07:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/kea/src/lib/hooks/callout_manager.cc
Line
Count
Source
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
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 <algorithm>
16
#include <climits>
17
#include <functional>
18
#include <utility>
19
20
using namespace std;
21
22
namespace isc {
23
namespace hooks {
24
25
// Constructor
26
CalloutManager::CalloutManager(int num_libraries)
27
79.4k
    : server_hooks_(ServerHooks::getServerHooks()), current_library_(-1),
28
79.4k
      hook_vector_(ServerHooks::getServerHooks().getCount()),
29
79.4k
      library_handle_(*this), pre_library_handle_(*this, 0),
30
79.4k
      post_library_handle_(*this, INT_MAX), num_libraries_(num_libraries) {
31
79.4k
    if (num_libraries < 0) {
32
0
        isc_throw(isc::BadValue, "number of libraries passed to the "
33
0
                  "CalloutManager must be >= 0");
34
0
    }
35
79.4k
}
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.
41
void
42
0
CalloutManager::checkLibraryIndex(int library_index) const {
43
0
    if (((library_index >= -1) && (library_index <= num_libraries_)) ||
44
0
        (library_index == INT_MAX)) {
45
0
        return;
46
0
    }
47
48
0
    isc_throw(NoSuchLibrary, "library index " << library_index <<
49
0
              " is not valid for the number of loaded libraries (" <<
50
0
              num_libraries_ << ")");
51
0
}
52
53
// Register a callout for the current library.
54
void
55
CalloutManager::registerCallout(const std::string& name,
56
                                CalloutPtr callout,
57
0
                                int library_index) {
58
    // Note the registration.
59
0
    LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUT_REGISTRATION)
60
0
        .arg(library_index).arg(name);
61
62
    // Sanity check that the current library index is set to a valid value.
63
0
    checkLibraryIndex(library_index);
64
65
    // New hooks could have been registered since the manager was constructed.
66
0
    ensureHookLibsVectorSize();
67
68
    // Get the index associated with this hook (validating the name in the
69
    // process).
70
0
    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
0
    for (CalloutVector::iterator i = hook_vector_[hook_index].begin();
76
0
         i != hook_vector_[hook_index].end(); ++i) {
77
0
        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
0
            hook_vector_[hook_index].insert(i, make_pair(library_index,
81
0
                                                         callout));
82
0
            return;
83
0
        }
84
0
    }
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
0
    hook_vector_[hook_index].push_back(make_pair(library_index, callout));
90
0
}
91
92
// Check if callouts are present for a given hook index.
93
bool
94
89.2k
CalloutManager::calloutsPresent(int hook_index) const {
95
    // Validate the hook index.
96
89.2k
    if ((hook_index < 0) ||
97
89.2k
        (static_cast<size_t>(hook_index) >= hook_vector_.size())) {
98
0
        isc_throw(NoSuchHook, "hook index " << hook_index <<
99
0
                  " is not valid for the list of registered hooks");
100
0
    }
101
102
    // Valid, so are there any callouts associated with that hook?
103
89.2k
    return (!hook_vector_[hook_index].empty());
104
89.2k
}
105
106
bool
107
21.7k
CalloutManager::commandHandlersPresent(const std::string& command_name) const {
108
    // Check if the hook point for the specified command exists.
109
21.7k
    int index = ServerHooks::getServerHooks().findIndex(
110
21.7k
                    ServerHooks::commandToHookName(command_name));
111
21.7k
    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
0
        return (calloutsPresent(index));
119
0
    }
120
121
    // Hook point not created, so we don't support this command in
122
    // any of the hooks libraries.
123
21.7k
    return (false);
124
21.7k
}
125
126
// Call all the callouts for a given hook.
127
void
128
38.2k
CalloutManager::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().
132
38.2k
    callout_handle.setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
133
134
    // Only initialize and iterate if there are callouts present.  This check
135
    // also catches the case of an invalid index.
136
38.2k
    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
0
        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
0
        util::Stopwatch stopwatch;
145
146
        // Mark that the callouts begin for the hook.
147
0
        LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_BEGIN)
148
0
            .arg(server_hooks_.getName(callout_handle.getCurrentHook()));
149
150
        // Call all the callouts.
151
0
        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
0
            callout_handle.setCurrentLibrary(i.first);
157
158
            // Call the callout
159
0
            try {
160
0
                stopwatch.start();
161
0
                int status = (*i.second)(callout_handle);
162
0
                stopwatch.stop();
163
0
                if (status == 0) {
164
0
                    LOG_DEBUG(callouts_logger, HOOKS_DBG_EXTENDED_CALLS,
165
0
                              HOOKS_CALLOUT_CALLED)
166
0
                        .arg(callout_handle.getCurrentLibrary())
167
0
                        .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
168
0
                        .arg(PointerConverter(i.second).dlsymPtr())
169
0
                        .arg(stopwatch.logFormatLastDuration());
170
0
                } else {
171
0
                    LOG_ERROR(callouts_logger, HOOKS_CALLOUT_ERROR)
172
0
                        .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
173
0
                        .arg(callout_handle.getCurrentLibrary())
174
0
                        .arg(PointerConverter(i.second).dlsymPtr())
175
0
                        .arg(stopwatch.logFormatLastDuration());
176
0
                }
177
0
            } 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
0
                stopwatch.stop();
181
                // Any exception, just ones based on isc::Exception
182
0
                LOG_ERROR(callouts_logger, HOOKS_CALLOUT_EXCEPTION)
183
0
                    .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
184
0
                    .arg(callout_handle.getCurrentLibrary())
185
0
                    .arg(PointerConverter(i.second).dlsymPtr())
186
0
                    .arg(e.what())
187
0
                    .arg(stopwatch.logFormatLastDuration());
188
0
                callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
189
0
            } catch (...) {
190
                // If an exception occurred, the stopwatch.stop() hasn't been
191
                // called, so we have to call it here.
192
0
                stopwatch.stop();
193
                // Any exception, not just ones based on isc::Exception
194
0
                LOG_ERROR(callouts_logger, HOOKS_CALLOUT_EXCEPTION)
195
0
                    .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
196
0
                    .arg(callout_handle.getCurrentLibrary())
197
0
                    .arg(PointerConverter(i.second).dlsymPtr())
198
0
                    .arg("Unspecified exception")
199
0
                    .arg(stopwatch.logFormatLastDuration());
200
0
                callout_handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
201
0
            }
202
0
        }
203
204
        // Mark end of callout execution. Include the total execution
205
        // time for callouts.
206
0
        LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_COMPLETE)
207
0
            .arg(server_hooks_.getName(callout_handle.getCurrentHook()))
208
0
            .arg(stopwatch.logFormatTotalDuration());
209
210
        // Reset the current hook and library indexes to an invalid value to
211
        // catch any programming errors.
212
0
        callout_handle.setCurrentHook(-1);
213
0
        callout_handle.setCurrentLibrary(-1);
214
0
    }
215
38.2k
}
216
217
void
218
CalloutManager::callCommandHandlers(const std::string& command_name,
219
0
                                    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.
224
0
    int index = ServerHooks::getServerHooks().getIndex(ServerHooks::commandToHookName(command_name));
225
    // Call the handlers for this command.
226
0
    callCallouts(index, callout_handle);
227
0
}
228
229
// Deregister a callout registered by the current library on a particular hook.
230
bool
231
CalloutManager::deregisterCallout(const std::string& name, CalloutPtr callout,
232
0
                                  int library_index) {
233
    // Sanity check that the current library index is set to a valid value.
234
0
    checkLibraryIndex(library_index);
235
236
    // New hooks could have been registered since the manager was constructed.
237
0
    ensureHookLibsVectorSize();
238
239
    // Get the index associated with this hook (validating the name in the
240
    // process).
241
0
    int hook_index = server_hooks_.getIndex(name);
242
243
    // New hooks can have been registered since the manager was constructed.
244
0
    if (static_cast<size_t>(hook_index) >= hook_vector_.size()) {
245
0
        return (false);
246
0
    }
247
248
    /// Construct a CalloutEntry matching the current library and the callout
249
    /// we want to remove.
250
0
    CalloutEntry target(library_index, callout);
251
252
    /// To decide if any entries were removed, we'll record the initial size
253
    /// of the callout vector for the hook, and compare it with the size after
254
    /// the removal.
255
0
    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
0
    hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
267
0
                                             hook_vector_[hook_index].end(),
268
0
                                             [&target] (CalloutEntry x) {
269
0
                                                 return (x == target); }),
270
0
                                   hook_vector_[hook_index].end());
271
272
    // Return an indication of whether anything was removed.
273
0
    bool removed = initial_size != hook_vector_[hook_index].size();
274
0
    if (removed) {
275
0
        LOG_DEBUG(callouts_logger, HOOKS_DBG_EXTENDED_CALLS,
276
0
                  HOOKS_CALLOUT_DEREGISTERED).arg(library_index).arg(name);
277
0
    }
278
279
0
    return (removed);
280
0
}
281
282
// Deregister all callouts on a given hook.
283
bool
284
CalloutManager::deregisterAllCallouts(const std::string& name,
285
0
                                      int library_index) {
286
    // New hooks could have been registered since the manager was constructed.
287
0
    ensureHookLibsVectorSize();
288
289
    // Get the index associated with this hook (validating the name in the
290
    // process).
291
0
    int hook_index = server_hooks_.getIndex(name);
292
293
    /// Construct a CalloutEntry matching the current library (the callout
294
    /// pointer is NULL as we are not checking that).
295
0
    CalloutEntry target(library_index, static_cast<CalloutPtr>(0));
296
297
    /// To decide if any entries were removed, we'll record the initial size
298
    /// of the callout vector for the hook, and compare it with the size after
299
    /// the removal.
300
0
    size_t initial_size = hook_vector_[hook_index].size();
301
302
    // Remove all callouts matching this library.
303
0
    hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
304
0
                                             hook_vector_[hook_index].end(),
305
0
                                             [&target] (CalloutEntry x) {
306
0
                                                 return (x.first == target.first);
307
0
                                             }),
308
0
                                   hook_vector_[hook_index].end());
309
310
    // Return an indication of whether anything was removed.
311
0
    bool removed = initial_size != hook_vector_[hook_index].size();
312
0
    if (removed) {
313
0
        LOG_DEBUG(callouts_logger, HOOKS_DBG_EXTENDED_CALLS,
314
0
                  HOOKS_ALL_CALLOUTS_DEREGISTERED).arg(library_index).arg(name);
315
0
    }
316
317
0
    return (removed);
318
0
}
319
320
void
321
0
CalloutManager::registerCommandHook(const std::string& command_name) {
322
    // New hooks could have been registered since the manager was constructed.
323
0
    ensureHookLibsVectorSize();
324
325
0
    ServerHooks& hooks = ServerHooks::getServerHooks();
326
0
    int hook_index = hooks.findIndex(ServerHooks::commandToHookName(command_name));
327
0
    if (hook_index < 0) {
328
        // Hook for this command doesn't exist. Let's create one.
329
0
        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
0
        hook_vector_.resize(server_hooks_.getCount());
336
0
    }
337
0
}
338
339
void
340
0
CalloutManager::ensureHookLibsVectorSize() {
341
0
    ServerHooks& hooks = ServerHooks::getServerHooks();
342
0
    if (static_cast<size_t>(hooks.getCount()) > hook_vector_.size()) {
343
        // Uh oh, there are more hook points that our vector allows.
344
0
        hook_vector_.resize(hooks.getCount());
345
0
    }
346
0
}
347
348
} // namespace util
349
} // namespace isc