summaryrefslogtreecommitdiffstats
path: root/src/lib/hooks/hooks_component_developer.dox
blob: baeb5ef94ec9885fbdd6e8772f3d57e1e47fdb54 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

/**
@page hooksComponentDeveloperGuide Guide to Hooks for the Kea Component Developer

@section hooksComponentIntroduction Introduction

The hooks framework is a Kea system that simplifies the way that
users can write code to modify the behavior of Kea.  Instead of
altering the Kea source code, they write functions that are compiled
and linked into one or more dynamic shared objects, called here (for
historical reasons), shared libraries.  The library is specified in the Kea
configuration and at run time Kea dynamically loads the library
into its address space.  At various points in the processing, the component
"calls out" to functions in the library, passing to them the data is it
currently working on.  They can examine and modify the data as required.

This guide is aimed at Kea developers who want to write or modify a
Kea component to use hooks.  It shows how the component should be written
to load a shared library at run-time and how to call functions in it.

For information about writing a hooks library containing functions called by Kea
during its execution, see the document @ref hooksdgDevelopersGuide.


@subsection hooksComponentTerminology Terminology

In the remainder of this guide, the following terminology is used:

- Component - a Kea process, e.g. the DHCPv4 or DHCPv6 server.

- Hook/Hook Point - used interchangeably, this is a point in the code at
which a call to user-written functions is made. Each hook has a name and
each hook can have any number (including 0) of user-written functions
attached to it.

- Callout - a user-written function called by the component at a hook
point. This is so-named because the component "calls out" to the library
to execute a user-written function.

- User code/user library - non-Kea code that is compiled into a
shared library and loaded by Kea into its address space.  Multiple
user libraries can be loaded at the same time, each containing callouts for
the same hooks.  The hooks framework calls these libraries one after the
other. (See the document @ref hooksdgDevelopersGuide for more details.)


@subsection hooksComponentLanguages Languages

The core of Kea is written in C++ with some remaining legacy parts in Python.
While it is the intention to provide the hooks framework for all languages,
the initial version is for C++. All examples in this guide are in that language.


@section hooksComponentBasicIdeas Basic Ideas

From the point of view of the component author, the basic ideas of the hooks
framework are quite simple:

- The location of hook points in the code need to be determined.

- Name the hook points and register them.

- At each hook point, the component needs to complete the following steps to
  execute callouts registered by the user-library:
  -# copy data into the object used to pass information to the callout.
  -# call the callout.
  -# copy data back from the object used to exchange information.
  -# take action based on information returned.

Of course, to set up the system the libraries need to be loaded in the first
place.  The component also needs to:

- Define the configuration item that specifies the user libraries for this
component.

- Handle configuration changes and load/unload the user libraries.

The following sections will describe these tasks in more detail.


@section hooksComponentDefinition Determining the Hook Points

Before any other action takes place, the location of the hook points
in the code need to be determined.  This, of course, depends on the
component but, as a general guideline, hook locations should be located
where a callout is able to obtain useful information from Kea and/or
affect processing.  Typically this means at the start or end of a major
step in the processing of a request, at a point where either useful
information can be passed to a callout and/or the callout can affect
the processing of the component. The latter is achieved in either or both
of the following ways:

- Setting the nest step status.  This is an enum that the callout can set
  and is a quick way of passing information back to the component.  It is used
  to indicate that the component should perform certain actions. Currently
  there are three statuses defined: CONTINUE (this is the default, the server
  is expected to continue as usual), SKIP (the server is expected to skip the
  next processing step, but otherwise continue as usual) and DROP (the server
  is expected to drop the packet or request completely. The exact action is up
  to the component.

- Modifying data passed to it.  The component should be prepared to continue
  processing with the data returned by the callout.  It is up to the component
  author whether the data is validated before being used, but doing so will
  have performance implications.


@section hooksComponentRegistration Naming and Registering the Hooks Points

Once the location of the hook point has been determined, it should be
given a name.  This name should be unique amongst all hook points and is
subject to certain restrictions (see below).

Before the callouts at any hook point are called and any user libraries
loaded - so typically during component initialization - the component must
register the names of all the hooks.  The registration is done using
the static method @c isc::hooks::HooksManager::registerHook():

@code

#include <hooks/hooks_manager.h>
    :
    int example_index = HooksManager::registerHook("lease_allocate");
@endcode

The name of the hook is passed as the sole argument to the @c registerHook()
method.  The value returned is the index of that hook point and should
be retained - it is needed to call the callouts attached to that hook.

Note that a hook only needs to be registered once.  There is no mechanism for
unregistering a hook and there is no need to do so.


@subsection hooksComponentAutomaticRegistration Automatic Registration of Hooks

In some components, it may be convenient to set up a single initialization
function that registers all hooks.  For others, it may be more convenient
for each module within the component to perform its own initialization.
Since the @c isc::hooks::HooksManager object is a singleton and is created when first
accessed, a useful trick is to automatically register the hooks when
the module is loaded.

This technique involves declaring an object outside of any execution
unit in the module.  When the module is loaded, the object's constructor
is run.  By placing the hook registration calls in the constructor,
the hooks in the module are defined at load time, before any function in
the module is run.  The code for such an initialization sequence would
be similar to:

@code
#include <hooks/hooks_manager.h>

namespace {

// Declare structure to perform initialization and store the hook indexes.
//
struct MyHooks {
    int pkt_rcvd;   // Index of "packet received" hook
    int pkt_sent;   // Index of "packet sent" hook

    // Constructor
    MyHooks() {
        pkt_rcvd = HooksManager::registerHook("pkt_rcvd");
        pkt_sent = HooksManager::registerHook("pkt_sent");
    }
};

// Declare a "MyHooks" object.  As this is outside any function or method, it
// will be instantiated (and the constructor run) when the module is loaded.
// As a result, the hook indexes will be defined before any method in this
// module is called.
MyHooks my_hooks;

} // Anonymous namespace

void Someclass::someFunction() {
    :
    // Check if any callouts are defined on the pkt_rcvd hook.
    if (HooksManager::calloutPresent(my_hooks.pkt_rcvd)) {
          :
    }
    :
}
@endcode


@subsection hooksComponentHookNames Hook Names

Hook names are strings and in principle, any string can be used as the
name of a hook, even one containing spaces and non-printable characters.
However, the following guidelines should be observed:

- The names <b>context_create</b> and <b>context_destroy</b> are reserved to
the hooks system and are automatically registered: an attempt to register
one of these will lead to a @c isc::hooks::DuplicateHook exception being thrown.

- The hook name should be a valid "C" function name.  If a user gives a
callout the same name as one of the hooks, the hooks framework will
automatically load that callout and attach it to the hook: the user does not
have to explicitly register it.

- The hook name should not conflict with the name of a function in any of
the system libraries (e.g. naming a hook "sqrt" could lead to the
square-root function in the system's maths library being attached to the hook
as a callout).

- Although hook names can be in any case (including mixed case), the Kea
convention is that they are lower-case.


@section hooksComponentCallingCallouts Calling Callouts on a Hook


@subsection hooksComponentArgument The Callout Handle

Before describing how to call user code at a hook point, we must first consider
how to pass data to it.

Each user callout has the signature:
@code
int callout_name(isc::hooks::CalloutHandle& handle);
@endcode

The @c isc::hooks::CalloutHandle object is the object used to pass data to
and from the callout.  This holds the data as a set of name/value pairs,
each pair being considered an argument to the callout.  If there are
multiple callouts attached to a hook, the @c CalloutHandle is passed to
each in turn. Should a callout modify an argument, the updated data is
passed subsequent callouts (each of which could also modify it) before
being returned to the component.

Two methods are provided to get and set the arguments passed to
the callout called (naturally enough) @c getArgument and @c setArgument.
Their usage is illustrated by the following code snippets.

@code
    int count = 10;
    boost::shared_ptr<Pkt4> pktptr = ... // Set to appropriate value

    // Assume that "handle_ptr" has been created and is a pointer to a
    // CalloutHandle.
    handle_ptr->setArgument("data_count", count);
    handle_ptr->setArgument("inpacket", pktptr);

    // Call the hook code.  lease_assigned_index is the value returned from
    // HooksManager::registerHook() when the hook was registered.
    HooksManager::callCallouts(lease_assigned_index, *handle_ptr);

    // Retrieve the modified values
    handle_ptr->getArgument("data_count", count);
    handle_ptr->getArgument("inpacket", pktptr);
@endcode

As can be seen @c getArgument is used to retrieve data from the
@c CalloutHandle, and @c setArgument used to put data into it.  If a callout
wishes to alter data and pass it back to the component, it should retrieve
the data with getArgument, modify it, and call setArgument to send
it back.

There are a couple points to be aware of:

- The data type of the variable in the call to @c getArgument must
match the data type of the variable passed to the corresponding
@c setArgument <B>exactly</B>: using what would normally be considered
to be a "compatible" type is not enough.  For example, if the callout
passed an argument back to the component as an @c int and the component
attempted to retrieve it as a @c long, an exception would be thrown even
though any value that can be stored in an @c int will fit into a @c long.
This restriction also applies the "const" attribute but only as applied to
data pointed to by pointers, e.g. if an argument is defined as a @c char*,
an exception will be thrown if an attempt is made to retrieve it into
a variable of type @c const @c char*.  (However, if an argument is set as a
@c const @c int, it can be retrieved into an @c int.)  The documentation of
a hook point should detail the exact data type of each argument.

- If a pointer to an object is passed to a callout (either a "raw"
pointer, or a boost smart pointer (as in the example above), and the
underlying object is altered through that pointer, the change will be
reflected in the component even if the callout makes no call to setArgument.
This can be avoided by passing a pointer to a "const" object.


@subsection hooksComponentSkipFlag The Skip Flag (obsolete)


@subsection hooksComponentNextStep The next step status

Although information is passed back to the component from callouts through
@c CalloutHandle arguments, a common action for callouts is to inform the component
that its flow of control should be altered.  For example:

- In the DHCP servers, there is a hook at the point at which a lease is
  about to be assigned.  Callouts attached to this hooks may handle the
  lease assignment in special cases, in which case they set the next step
  status to SKIP to indicate that the server should not perform lease assignment
  in this case.
- A server may define a hook just after a packet is received.  A callout
  attached to the hook might inspect the source address and compare it
  against a blacklist.  If the address is on the list, the callout could set
  the DROP flag to indicate to the server that the packet should be dropped.

For ease of processing, the @c CalloutHandle contains
two methods, @c isc::hooks::CalloutHandle::getStatus() and
@c isc::hooks::CalloutHandle::setStatus().  It is only meaningful for the
component to use the "get" method.  The next step status is cleared (set to
the default value of CONTINUE) by the hooks framework when the component
requests that callouts be executed, so any
value set by the component is lost.  Callouts can both inspect the status (it
might have been set by callouts earlier in the callout list for the hook)
and set it.  Note that the setting of the status by a callout does not
prevent callouts later in the list from being called: the next step status is
just an enum value - the only significance comes from its interpretation
by the component.

An example of use could be:
@code
// Set up arguments for DHCP lease assignment.
handle->setArgument("query", query);
handle->setArgument("response", response);
HooksManager::callCallouts(lease_hook_index, *handle_ptr);
if (handle_ptr->getStatus() != CalloutHandle::NEXT_STEP_SKIP) {
    // Skip flag not set, do the address allocation
    :
}
@endcode


@subsection hooksComponentGettingHandle Getting the Callout Handle

The @c CalloutHandle object is linked to the loaded libraries
for lifetime reasons (described below).  Components
should retrieve a @c isc::hooks::CalloutHandle using
@c isc::hooks::HooksManager::createCalloutHandle():
@code
    CalloutHandlePtr handle_ptr = HooksManager::createCalloutHandle();
@endcode
(@c isc::hooks::CalloutHandlePtr is a typedef for a Boost shared pointer to a
CalloutHandle.)  The CalloutHandle so retrieved may be used for as
long as the libraries are loaded.

The handle is deleted by resetting the pointer:
@code
    handle_ptr.reset();
@endcode
... or by letting the handle pointer go out of scope.  The actual deletion
occurs when the CallHandle's reference count goes to zero. (The
current version of the hooks framework does not maintain any other
pointer to the returned CalloutHandle, so it gets destroyed when the
shared pointer to it is cleared or destroyed.  However, this may change
in a future version.)

When the handle is not created locally it is not destroyed so it can
keep ownership on arguments. In such case the code must call @c
isc::hooks::CalloutHandle::deleteAllArguments or simply use the RAII
helper @c isc::hooks::ScopedCalloutHandleState as in:
@code
    CalloutHandlePtr handle_ptr = getCalloutHandle(query);
    ScopedCalloutHandleState state(handle_ptr);
@endcode

@subsection hooksComponentCallingCallout Calling the Callout

Calling the callout is a simple matter of executing the
@c isc::hooks::HooksManager::callCallouts() method for the hook index in
question.  For example, with the hook index "pkt_sent" defined as above,
the hook can be executed by:
@code
    HooksManager::callCallouts(pkt_sent, *handle_ptr);
@endcode
... where "*handle_ptr" is a reference (note: not a pointer) to the
@c isc::hooks::CalloutHandle object holding the arguments.  No status code
is returned.  If a component needs to get data returned (other than that
provided by the next step status), it should define an argument through which
the callout can do so.

@subsubsection hooksComponentConditionalCallout Conditionally Calling Hook Callouts

Most hooks in a component will not have callouts attached to them. To
avoid the overhead of setting up arguments in the @c CalloutHandle, a
component can check for callouts before doing that processing using
@c isc::hooks::HooksManager::calloutsPresent().  Taking the index of a
hook as its sole argument, the function returns true if there are any
callouts attached to the hook and false otherwise.

With this check, the code in the component for calling a hook would look
something like:
@code
if (HooksManager::calloutsPresent(lease_hook_index)) {
    // Set up arguments for lease assignment
    handle->setArgument("query", query);
    handle->setArgument("response", response);
    HooksManager::callCallouts(lease_hook_index, *handle);
    if (handle->getStatus() != CalloutHandle::NEXT_STEP_DROP) {
        // Next step allows us to continue, do the address allocation
        :
    }
}
@endcode

@section hooksComponentLoadLibraries Loading the User Libraries

Once hooks are defined, all the hooks code described above will
work, even if no libraries are loaded (and even if the library
loading method is not called).  The @c CalloutHandle returned by
@c isc::hooks::HooksManager::createCalloutHandle() will be valid,
@c isc::hooks::HooksManager::calloutsPresent() will return false for every
index, and @c isc::hooks::HooksManager::callCallouts() will be a no-op.

However, if user libraries are specified in the Kea configuration,
the component should load them.  (Note the term "libraries": the hooks
framework allows multiple user libraries to be loaded.) This should take
place after the component's configuration has been read, and is achieved
by the @c isc::hooks::HooksManager::loadLibraries() method.  The method is
passed a vector of strings, each giving the full file specification of
a user library:
@code
    std::vector<std::string> libraries = ... // Get array of libraries
    bool success = HooksManager::loadLibraries(libraries);
@endcode
@c loadLibraries() returns a boolean status which is true if all libraries
loaded successfully or false if one or more failed to load.  Appropriate
error messages will have been logged in the latter case, the status
being more to allow the developer to decide whether the execution
should proceed in such circumstances.

Before @c loadLibraries() can be called a second or subsequent time
(as a result of a reconfiguration), all existing libraries must be
successfully unloaded. If a library stays in memory from a programming
bug in Kea (for instance when no libraries were loaded) or in a
library (@ref hooksMemoryManagement) @c loadLibraries() throws a not
recoverable error.

Unloading is done in two phases since Kea version 1.7.10:

- call to @c isc::hooks::HooksManager::prepareUnloadLibraries() which
calls all unload() entry points and deregisters callout points.

- call to @c isc::hooks::HooksManager::unloadLibraries() even when
the prepare failed.

If a failure of @c unloadLibraries() is ignored any call to @c loadLibraries()
will throw.


@subsection hooksComponentUnloadIssues Unload and Reload Issues

Unloading a shared library works by unmapping the part of the process's
virtual address space in which the library lies.  This may lead to
problems if there are still references to that address space elsewhere
in the process.

In many operating systems, heap storage allowed by a shared library will
lie in the virtual address allocated to the library.  This has implications
in the hooks framework because:

- Argument information stored in a @c CalloutHandle by a callout in a library
may lie in the library's address space.
- Data modified in objects passed as arguments may lie in the address
space.  For example, it is common for a DHCP callout to add "options"
to a packet: the memory allocated for those options will most likely
lie in library address space.

The problem really arises because of the extensive use by Kea of boost
smart pointers.  When the pointer is destroyed, the pointed-to memory is
deallocated.  If the pointer points to address space that is unmapped because
a library has been unloaded, the deletion causes a segmentation fault.

The hooks framework addresses the issue for CalloutHandles by keeping in
that object a shared pointer to the object controlling library unloading.
Although a library can be unloaded at any time, it is only when all
CalloutHandles that could possibly reference address space in the library
have been deleted that the library will actually be unloaded and the
address space unmapped.

The hooks framework cannot solve the second issue as the objects in
question are under control of the component.  It is up to the component
developer to ensure that all such objects have been destroyed before
libraries are reloaded.  In extreme cases this may mean the component
suspending all processing of incoming requests until all currently
executing requests have completed and data object destroyed, reloading
the libraries, then resuming processing.

Since Kea 1.7.10 the unload() entry point is called as the first phase
of unloading. This gives more chance to hooks writer to perform
necessary cleanup actions so the second phase, memory unmapping
can safely happen. The @c isc::hooks::unloadLibraries() function
was updated too to return false when at least one active callout
handle remained.


@section hooksComponentCallouts Component-Defined Callouts

Previous sections have discussed callout registration by user libraries.
It is possible for a component to register its own functions (i.e. within
its own address space) as hook callouts.  These functions are called
in exactly the same way as user callouts, being passed their arguments
though a CalloutHandle object.  (Guidelines for writing callouts can be
found in @ref hooksdgDevelopersGuide.)

A component can associate with a hook callouts that run either before
user-registered callouts or after them.  Registration is done via a
@c isc::hooks::LibraryHandle object, a reference to one being obtained
through the methods @c isc::hooks::HooksManager::preCalloutLibraryHandle()
(for a handle to register callouts to run before the user library
callouts) or @c isc::hooks::HooksManager::postCalloutLibraryHandle() (for
a handle to register callouts to run after the user callouts).  Use of
the @c LibraryHandle to register and deregister callouts is described in
@ref hooksdgLibraryHandle.

Finally, it should be noted that callouts registered in this way only
remain registered until the next call to @c isc::hooks::loadLibraries().
It is up to the component to re-register the callouts after this
method has been called.

*/