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
|
// 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/.
// Note: the prefix "hooksmg" to all labels is an abbreviation for "Hooks
// Maintenance Guide" and is used to prevent a clash with symbols in any
// other Doxygen file.
/**
@page hooksmgMaintenanceGuide Hooks Maintenance Guide
@section hooksmgIntroduction Introduction
This document is aimed at Kea maintainers responsible for the hooks
system. It provides an overview of the classes that make up the hooks
framework and notes important aspects of processing. More detailed
information can be found in the source code.
It is assumed that the reader is familiar with the contents of the @ref
hooksdgDevelopersGuide and the @ref hooksComponentDeveloperGuide.
@section hooksmgObjects Hooks Framework Objects
The relationships between the various objects in the hooks framework
is shown below:
@image html HooksUml.png "High-Level Class Diagram of the Hooks Framework"
(To avoid clutter, the @ref hooksmgServerHooks object, used to pass
information about registered hooks to the components, is not shown on
the diagram.)
The hooks framework objects can be split into user-side objects and
server-side objects. The former are those objects used or referenced
by user-written hooks libraries. The latter are those objects used in
the hooks framework.
@subsection hooksmgUserObjects User-Side Objects
The user-side code is able to access two objects in the framework,
the @ref hooksmgCalloutHandle and the @ref hooksmgLibraryHandle.
The @ref hooksmgCalloutHandle is used to pass data between the Kea
component and the loaded library; the @ref hooksmgLibraryHandle is used
for registering callouts.
@subsubsection hooksmgCalloutHandle Callout Handle
The @ref isc::hooks::CalloutHandle has two functions: passing arguments
between the Kea component and the user-written library, and storing
per-request context between library calls. In both cases the data is
stored in a @c std::map structure, keyed by argument (or context item) name.
The actual data is stored in a @c boost::any object, which allows any
data type to be stored, although a penalty for this flexibility is
the restriction (mentioned in the @ref hooksdgDevelopersGuide) that
the type of data retrieved must be identical (and not just compatible)
with that stored.
The storage of context data is slightly complex because there is
separate context for each user library. For this reason, the @ref
hooksmgCalloutHandle has multiple maps, one for each library loaded.
The maps are stored in another map, the appropriate map being identified
by the "current library index" (this index is explained further below).
The reason for the second map (rather than a structure such as a vector)
is to avoid creating individual context maps unless needed; given the
key to the map (in this case the current library index) accessing an
element in a map using the operator[] method returns the element in
question if it exists, or creates a new one (and stores it in the map)
if its doesn't.
@subsubsection hooksmgLibraryHandle Library Handle
Little more than a restricted interface to the @ref
hooksmgCalloutManager, the @ref isc::hooks::LibraryHandle allows a
callout to register and deregister callouts. However, there are some
quirks to callout registration which, although the processing involved
is in the @ref hooksmgCalloutManager, are best described here.
Firstly, a callout can be deregistered by a function within a user
library only if it was registered by a function within that library. That
is to say, if library A registers the callout A_func() on hook "alpha"
and library B registers B_func(), functions within library A are only
able to remove A_func() (and functions in library B remove B_func()).
The restriction - here to prevent one library interfering with the
callouts of another - is enforced by means of the current library index.
As described below, each entry in the vector of callouts associated with
a hook is a pair object, comprising a pointer to the callout and
the index of the library with which it is associated. A callout
can only modify entries in that vector where the current library index
matches the index element of the pair.
A second quirk is that when dynamically modifying the list of callouts,
the change only takes effect when the current call out from the server
completes. To clarify this, suppose that functions A_func(), B_func()
and C_func() are registered on a hook, and the server executes a callout
on the hook. Suppose also during this call, A_func() removes the callout
C_func() and that B_func() adds D_func(). As changes only take effect
when the current call out completes, the user callouts executed will be
A_func(), B_func() then C_func(). When the server calls the hook callouts
again, the functions executed will be A_func(), B_func() and D_func().
This restriction is down to implementation. When a set of callouts on a hook
is being called, the @ref hooksmgCalloutManager iterates through a
vector (the "callout vector") of (index, callout pointer) pairs. Since
registration or deregistration of a callout on that hook would change the
vector (and so potentially invalidate the iterators used to access the it),
a copy of the vector is taken before the iteration starts. The @ref
hooksmgCalloutManager iterates over this copy while any changes made
by the callout registration functions affect the relevant callout vector.
Such approach was chosen because of performance considerations.
@subsection hooksmgServerObjects Server-Side Objects
Those objects are not accessible by user libraries. Please do not
attempt to use them if you are developing user callouts.
@subsubsection hooksmgServerHooks Server Hooks
The singleton @ref isc::hooks::ServerHooks object is used to register
hooks. It is little more than a wrapper around a map of (hook index,
hook name), generating a unique number (the hook index) for each
hook registered. It also handles the registration of the pre-defined
context_create and context_destroy hooks.
In operation, the @ref hooksmgHooksManager provides a thin wrapper
around it, so that the Kea component developer does not have to
worry about another object.
@subsubsection hooksmgLibraryManager Library Manager
An @ref isc::hooks::LibraryManager is created by the @ref
hooksmgHooksManager object for each shared library loaded. It
controls the loading and unloading of the library and in essence
represents the library in the hooks framework. It also handles the
registration of the standard callouts (functions in the library with
the same name as the hook name).
Of particular importance is the "library's index", a number associated
with the library. This is passed to the LibraryManager at creation
time and is used to tag the callout pointers. It is discussed
further below.
As the @c LibraryManager provides all the methods needed to manage the
shared library, it is the natural home for the static @c validateLibrary()
method. The function called the parsing of the Kea configuration, when
the "hooks-libraries" element is processed. It checks that shared library
exists, that it can be opened, that it contains the @c version() function
and that that function returns a valid value. It then closes the shared
library and returns an appropriate indication as to the library status.
@subsubsection hooksmgLibraryManagerCollection Library Manager Collection
The hooks framework can handle multiple libraries and as
a result will create a @ref hooksmgLibraryManager for each
of them. The collection of LibraryManagers is managed by the
@ref isc::hooks::LibraryManagerCollection object which, in most
cases has a method corresponding to a @ref hooksmgLibraryManager
method, e.g. it has a @c loadLibraries() that corresponds to the @ref
hooksmgLibraryManager's loadLibrary() call. As would be expected, methods
on the @c LibraryManagerCollection iterate through all specified libraries,
calling the corresponding LibraryManager method for each library.
One point of note is that @c LibraryManagerCollection operates on an "all
or none" principle. When @c loadLibraries() is called, on exit either all
libraries have been successfully opened or none of them have. There
is no use-case in Kea where, after a user has specified the shared
libraries they want to load, the system will operate with only some of
them loaded.
The @c LibraryManagerCollection is the place where each library's index is set.
Each library is assigned a number ranging from 1 through to the number
of libraries being loaded. As mentioned in the previous section, this
index is used to tag callout pointers, something that is discussed
in the next section.
(Whilst on the subject of library index numbers, two additional
numbers - 0 and @c INT_MAX - are also valid as "current library index".
For flexibility, the Kea component is able to register its own
functions as hook callouts. It does this by obtaining a suitable @ref
hooksmgLibraryHandle from the @ref hooksmgHooksManager. A choice
of two is available: one @ref hooksmgLibraryHandle (with an index
of 0) can be used to register a callout on a hook to execute before
any user-supplied callouts. The second (with an index of @c INT_MAX)
is used to register a callout to run after user-specified callouts.
Apart from the index number, the hooks framework does not treat these
callouts any differently from user-supplied ones.)
@subsubsection hooksmgCalloutManager Callout Manager
The @ref isc::hooks::CalloutManager is the core of the framework insofar
as the registration and calling of callouts is concerned.
It maintains a "hook vector" - a vector with one element for
each registered hook. Each element in this vector is itself a
vector (the callout vector), each element of which is a pair of
(library index, callback pointer). When a callout is registered, the
CalloutManager's current library index is used to supply the "library
index" part of the pair. The library index is set explicitly by the
@ref hooksmgLibraryManager prior to calling the user library's load()
function (and prior to registering the standard callbacks).
The situation is slightly more complex when a callout is executing. In
order to execute a callout, the CalloutManager's @c callCallouts()
method must be called. This iterates through the callout vector for
a hook and for each element in the vector, uses the "library index"
part of the pair to set the "current library index" before calling the
callout function recorded in the second part of the pair. In most cases,
the setting of the library index has no effect on the callout. However,
if the callout wishes to dynamically register or deregister a callout,
the @ref hooksmgLibraryHandle (see above) calls a method on the
@ref hooksmgCalloutManager which in turn uses that information.
@subsubsection hooksmgHooksManager Hooks Manager
The @ref isc::hooks::HooksManager is the main object insofar as the
server is concerned. It controls the creation of the library-related
objects and provides the framework in which they interact. It also
provides a shell around objects such as @ref hooksmgServerHooks so that all
interaction with the hooks framework by the server is through the
HooksManager object. Apart from this, it supplies no functionality to
the hooks framework.
@section hooksmgOtherIssues Other Issues
@subsection hooksmgMemoryAllocation Memory Allocation
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 @ref hooksmgCalloutHandle 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 the @ref hooksmgCalloutHandle
by keeping in that object a shared pointer to the object controlling
library unloading (the @ref hooksmgLibraryManagerCollection). Although
the libraries can be unloaded at any time, it is only when every
@ref hooksmgCalloutHandle 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 Kea server incorporating the
hooks. It is up to the server developer to ensure that all such objects
have been destroyed before libraries are reloaded. In extreme cases
this may mean the server 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.
@subsection hooksmgStaticLinking Hooks and Statically-Linked Kea
Kea has the configuration option to allow static linking. What this
means is that it links against the static Kea libraries and not
the sharable ones - although it links against the sharable system
libraries like "libc" and "libstdc++" and well as the sharable libraries
for third-party packages such as log4cplus and MySql.
Static linking poses a problem for dynamically-loaded hooks libraries
as some of the code in them - in particular the hooks framework and
the logging code - depend on global objects created within the Kea
libraries. In the normal course of events (Kea linked against
shared libraries), when Kea is run and the operating system loads
a Kea shared library containing a global object, address space
is assigned for it. When the hooks framework loads a user-library
linked against the same Kea shared library, the operating system
recognizes that the library is already loaded (and initialized) and
uses its definition of the global object. Thus both the code in the
Kea image and the code in the user-written shared library
reference the same object.
If Kea is statically linked, the linker allocates address space
in the Kea image for the global object and does not include any
reference to the shared library containing it. When Kea now loads
the user-written shared library - and so loads the Kea library code
containing the global object - the operating system does not know that
the object already exists. Instead, it allocates new address space.
The version of Kea in memory therefore has two copies of the object:
one referenced by code in the Kea image, and one referenced by code
in the user-written hooks library. This causes problems - information
put in one copy is not available to the other.
Particular problems were encountered with global objects the hooks library
and in the logging library, so some code to alleviate the problem has been
included.
The issue in the hooks library is the singleton @ref
isc::hooks::ServerHooks object, used by the user-written hooks library
if it attempts to register or deregister callouts. The contents of the
singleton - the names of the hook points and their index - are set by
the relevant Kea server; this information is not available in the
singleton created in the user's hooks library.
Within the code users by the user's hooks library, the @c ServerHooks
object is used by @ref isc::hooks::CalloutHandle and @ref
isc::hooks::CalloutManager objects. Both these objects are passed to the
hooks library code when a callout is called: the former directly through
the callout argument list, the latter indirectly as a pointer to it is
stored in the CalloutHandle. This allows a solution to the problem:
instead of accessing the singleton via @c ServerHooks::getServerHooks(),
the constructors of these objects store a reference to the singleton
@c ServerHooks when they are created and use that reference to access
@c ServerHooks data. Since both @c CalloutHandle and @c CalloutManager are
created in the statically-linked Kea server, use of the reference
means that it is the singleton within the server - and not the one
within the user's hooks library - that is referenced.
The solution of the logging problem is not so straightforward. Within
Kea, there are two logging components, the Kea logging framework
and the log4cplus libraries. Owing to static linking, there are two
instances of the former; but as static linking uses shared libraries of
third-party products, there is one instance of the latter. What further
complicates matters is that initialization of the logging framework is
in two parts: static initialization and run-time initialization.
The logging initialization comprises the following:
-# Static initialization of the log4cplus global variables.
-# Static initialization of messages in the various Kea libraries.
-# Static initialization of logging framework.
-# Run-time initialization of the logging framework.
-# Run-time initialization of log4cplus
As both the Kea server and the user-written hooks libraries use the
log4cplus shared library, item 1 - the static initialization of the log4cplus
global variables is performed once.
The next two tasks - static initialization of the messages in the Kea
libraries and the static initialization of the logging framework -
are performed twice, once in the context of the Kea server and
once in the context of the hooks library. For this reason, run-time
initialization of the logging framework needs to be performed twice,
once in the context of the Kea server and once in the context of the
user-written hooks library. However, the standard logging framework
initialization code also performs the last task, initialization of
log4cplus, something that causes problems if executed more than once.
To get round this, the function @ref isc::hooks::hooksStaticLinkInit()
has been written. It executes the only part of the logging framework
run-time initialization that actually pertains to the logging framework
and not log4cplus, namely loading the message dictionary with the
statically-initialized messages in the Kea libraries.
This should be executed by any hooks library linking against a statically
initialized Kea. (In fact, running it against a dynamically-linked
Kea should have no effect, as the load operation discards any duplicate
message entries.) The hooks library tests do this, the code being
conditionally compiled within a test of the @c USE_STATIC_LINK macro, set
by the configure script.
@note Not everything is completely rosy with logging and static linking.
In particular, there appears to be an issue with the scenario where a
user-written hooks library is run by a statically-linked Kea and then
unloaded. As far as can be determined, on unload the system attempts to
delete the same logger twice. This is alleviated by explicitly clearing
the loggerptr_ variable in the @ref isc::log::Logger destructor, but there
is a suspicion that some memory might be lost in these circumstances.
This is still under investigation.
*/
|