hooks_user.dox 55.5 KB
Newer Older
1
// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
2
//
3 4 5
// 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/.
6

7 8 9 10
// Note: the prefix "hooksdg" to all labels is an abbreviation for "Hooks
// Developer's Guide" and is used to prevent a clash with symbols in any
// other Doxygen file.

11
/**
12
 @page hooksdgDevelopersGuide Hooks Developer's Guide
13

14
 @section hooksdgIntroduction Introduction
15

16
Although the Kea framework and its DHCP programs
17 18 19 20
provide comprehensive functionality, there will be times when it does
not quite do what you require: the processing has to be extended in some
way to solve your problem.

21
Since the Kea source code is freely available (Kea being an
22 23 24
open-source project), one option is to modify it to do what
you want.  Whilst perfectly feasible, there are drawbacks:

25
- Although well-documented, Kea is a large program.  Just
26 27 28 29
understanding how it works will take a significant amount of time. In
addition, despite the fact that its object-oriented design keeps the
coupling between modules to a minimum, an inappropriate change to one
part of the program during the extension could cause another to
30
behave oddly or to stop working altogether.
31 32

- The change may need to be re-applied or re-written with every new
33
version of Kea.  As new functionality is added or bugs are fixed,
34 35 36
the code or algorithms in the core software may change - and may change
significantly.

37
To overcome these problems, Kea provides the "Hooks" interface -
38 39 40
a defined interface for third-party or user-written code. (For ease of
reference in the rest of this document, all such code will be referred
to as "user code".)  At specific points in its processing
41
("hook points") Kea will make a call to this code.  The call passes
42
data that the user code can examine and, if required, modify.
43
Kea uses the modified data in the remainder of its processing.
44

45 46 47 48 49 50
In order to minimize the interaction between Kea and the user code,
the latter is built independently of Kea in the form of one or more
dynamic shared objects, called here (for historical reasons), shared
libraries.  These are made known to Kea through its configuration
mechanism, and Kea loads the library at run time. Libraries can be
unloaded and reloaded as needed while Kea is running.
51

52 53
Use of a defined API and the Kea configuration mechanism means that
as new versions of Kea are released, there is no need to modify
54 55 56
the user code.  Unless there is a major change in an interface
(which will be clearly documented), all that will be required is a rebuild
of the libraries.
57 58

@note Although the defined interface should not change, the internals
59
of some of the classes and structures referenced by the user code may
60
change between versions of Kea.  These changes have to be reflected
61
in the compiled version of the software, hence the need for a rebuild.
62

63
@subsection hooksdgLanguages Languages
64

65
The core of Kea is written in C++.  While it is the intention to
66 67 68
provide interfaces into user code written in other languages, the initial
versions of the Hooks system requires that user code be written in C++.
All examples in this guide are in that language.
69

70
@subsection hooksdgTerminology Terminology
71 72 73 74

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

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

79
- Callout - a user function called by the server at a hook
80
point. This is so-named because the server "calls out" to the library
81
to execute a user function.
82

83 84
- Framework function - the functions that a user library needs to
supply in order for the hooks framework to load and unload the library.
85

86 87
- User code/user library - non-Kea code that is compiled into a
shared library and loaded by Kea into its address space.
88 89


90
@section hooksdgTutorial Tutorial
91

92
To illustrate how to write code that integrates with Kea, we will
93 94
use the following (rather contrived) example:

95
<i>The Kea DHCPv4 server is used to allocate IPv4 addresses to clients
96 97 98
(as well as to pass them other information such as the address of DNS
servers).  We will suppose that we need to classify clients requesting
IPv4 addresses according to their hardware address, and want to log both
99
the hardware address and allocated IP address for the clients of interest.</i>
100 101

The following sections describe how to implement these requirements.
102 103
The code presented here is not efficient and there are better ways of
doing the task.  The aim however, is to illustrate the main features of
104
user hooks code, not to provide an optimal solution.
105 106


107
@subsection hooksdgFrameworkFunctions Framework Functions
108

109
Loading and initializing a library holding user code makes use
110 111
of three (user-supplied) functions:

112
- version - defines the version of Kea code with which the user-library
113 114 115 116
is built
- load - called when the library is loaded by the server.
- unload - called when the library is unloaded by the server.

117
Of these, only "version" is mandatory, although in our example, all three
118 119
are used.

120
@subsubsection hooksdgVersionFunction The "version" Function
121 122

"version" is used by the hooks framework to check that the libraries
123 124
it is loading are compatible with the version of Kea being run.
Although the hooks system allows Kea and user code to interface
125
through a defined API, the relationship is somewhat tight in that the
126 127
user code will depend on the internal structures of Kea.  If these
change - as they can between Kea releases - and Kea is run with
128
a version of user code built against an earlier version of Kea, a program
129
crash could result.
130

131 132
To guard against this, the "version" function must be provided in every
library.  It returns a constant defined in header files of the version
133 134
of Kea against which it was built.  The hooks framework checks this
for compatibility with the running version of Kea before loading
135
the library.
136 137 138 139 140 141 142 143 144 145 146 147

In this tutorial, we'll put "version" in its own file, version.cc.  The
contents are:

@code
// version.cc

#include <hooks/hooks.h>

extern "C" {

int version() {
148
    return (KEA_HOOKS_VERSION);
149 150
}

151
}
152 153
@endcode

154
The file "hooks/hooks.h" is specified relative to the Kea libraries
155
source directory - this is covered later in the section @ref hooksdgBuild.
156 157
It defines the symbol KEA_HOOKS_VERSION, which has a value that changes
on every release of Kea: this is the value that needs to be returned
158
to the hooks framework.
159 160 161 162 163 164 165

A final point to note is that the definition of "version" is enclosed
within 'extern "C"' braces.  All functions accessed by the hooks
framework use C linkage, mainly to avoid the name mangling that
accompanies use of the C++ compiler, but also to avoid issues related
to namespaces.

166
@subsubsection hooksdgLoadUnloadFunctions The "load" and "unload" Functions
167 168

As the names suggest, "load" is called when a library is loaded and
169 170
"unload" called when it is unloaded.  (It is always guaranteed that
"load" is called: "unload" may not be called in some circumstances,
171
e.g., if the system shuts down abnormally.)  These functions are the
172 173 174 175
places where any library-wide resources are allocated and deallocated.
"load" is also the place where any callouts with non-standard names
(names that are not hook point names) can be registered:
this is covered further in the section @ref hooksdgCalloutRegistration.
176 177

The example does not make any use callouts with non-standard names.  However,
178
as our design requires that the log file be open while Kea is active
179
and the library loaded, we'll open the file in the "load" function and close
180 181 182
it in "unload".

We create two files, one for the file handle declaration:
183 184 185 186 187 188

@code
// library_common.h

#ifndef LIBRARY_COMMON_H
#define LIBRARY_COMMON_H
189

190 191 192 193
#include <fstream>

// "Interesting clients" log file handle declaration.
extern std::fstream interesting;
194 195

#endif // LIBRARY_COMMON_H
196 197 198 199 200 201 202 203 204 205
@endcode

... and one to hold the "load" and "unload" functions:

@code
// load_unload.cc

#include <hooks/hooks.h>
#include "library_common.h"

206 207
using namespace isc::hooks;

208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
// "Interesting clients" log file handle definition.
std::fstream interesting;

extern "C" {

int load(LibraryHandle&) {
    interesting.open("/data/clients/interesting.log",
                     std::fstream::out | std::fstream::app);
    return (interesting ? 0 : 1);
}

int unload() {
    if (interesting) {
        interesting.close();
    }
    return (0);
}

226
}
227 228 229 230 231 232 233
@endcode

Notes:
- The file handle ("interesting") is declared in a header file and defined
outside of any function.  This means it can be accessed by any function
within the user library.  For convenience, the definition is in the
load_unload.cc file.
234 235 236 237 238 239
- "load" is called with a LibraryHandle argument, this being used in
the registration of functions.  As no functions are being registered
in this example, the argument specification omits the variable name
(whilst retaining the type) to avoid an "unused variable" compiler
warning. (The LibraryHandle and its use is discussed in the section
@ref hooksdgLibraryHandle.)
240 241
- In the current version of the hooks framework, it is not possible to pass
any configuration information to the "load" function.  The name of the log
242 243 244
file must therefore be hard-coded as an absolute path name or communicated
to the user code by some other means.
- "load" must 0 on success and non-zero on error.  The hooks framework
245 246 247 248
will abandon the loading of the library if "load" returns an error status.
(In this example, "interesting" can be tested as a boolean value,
returning "true" if the file opened successfully.)
- "unload" closes the log file if it is open and is a no-op otherwise. As
249
with "load", a zero value must be returned on success and a non-zero value
250
on an error.  The hooks framework will record a non-zero status return
251
as an error in the current Kea log but otherwise ignore it.
252 253
- As before, the function definitions are enclosed in 'extern "C"' braces.

254
@subsection hooksdgCallouts Callouts
255 256 257

Having sorted out the framework, we now come to the functions that
actually do something.  These functions are known as "callouts" because
258
the Kea code "calls out" to them.  Each Kea server has a number of
259 260 261
hooks to which callouts can be attached: server-specific documentation
describes in detail the points in the server at which the hooks are
present together with the data passed to callouts attached to them.
262 263

Before we continue with the example, we'll discuss how arguments are
264 265
passed to callouts and information is returned to the server.  We will
also discuss how information can be moved between callouts.
266

267
@subsubsection hooksdgCalloutSignature The Callout Signature
268 269 270 271 272

All callouts are declared with the signature:
@code
extern "C" {
int callout(CalloutHandle& handle);
273
}
274 275 276
@endcode

(As before, the callout is declared with "C" linkage.)  Information is passed
277
between Kea and the callout through name/value pairs in the @c CalloutHandle
278 279 280
object. The object is also used to pass information between callouts on a
per-request basis. (Both of these concepts are explained below.)

281
A callout returns an @c int as a status return.  A value of 0 indicates
282 283 284 285 286
success, anything else signifies an error.  The status return has no
effect on server processing; the only difference between a success
and error code is that if the latter is returned, the server will
log an error, specifying both the library and hook that generated it.
Effectively the return status provides a quick way for a callout to log
287
error information to the Kea logging system.
288

289
@subsubsection hooksdgArguments Callout Arguments
290

291
The @c CalloutHandle object provides two methods to get and set the
292
arguments passed to the callout.  These methods are called (naturally
Francis Dupont's avatar
Francis Dupont committed
293
enough) getArgument and setArgument.  Their usage is illustrated by the
294 295 296 297 298 299 300 301 302 303 304 305
following code snippets.

@code
    // Server-side code snippet to show the setting of arguments

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

    // Assume that "handle" has been created
    handle.setArgument("data_count", count);
    handle.setArgument("inpacket", pktptr);

306
    // Call the callouts attached to the hook
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
    ...

    // Retrieve the modified values
    handle.getArgument("data_count", count);
    handle.getArgument("inpacket", pktptr);
@endcode

In the callout

@code
    int number;
    boost::shared_ptr<Pkt4> packet;

    // Retrieve data set by the server.
    handle.getArgument("data_count", number);
    handle.getArgument("inpacket", packet);

    // Modify "number"
    number = ...;

    // Update the arguments to send the value back to the server.
    handle.setArgument("data_count", number);
@endcode

331 332
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
333
wishes to alter data and pass it back to the server, it should retrieve
334
the data with @c getArgument, modify it, and call @c setArgument to send
335 336 337 338
it back.

There are several points to be aware of:

339 340
- 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
341 342
<B>exactly</B>: using what would normally be considered to be a
"compatible" type is not enough.  For example, if the server passed
343 344 345
an argument as an @c int and the callout 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
346
applies the "const" attribute but only as applied to data pointed to by
347
pointers, e.g., if an argument is defined as a @c char*, an exception will
348
be thrown if an attempt is made to retrieve it into a variable of type
349 350 351
@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 each hook
point will detail the data type of each argument.
352 353 354 355 356 357
- Although all arguments can be modified, some altered values may not
be read by the server. (These would be ones that the server considers
"read-only".) Consult the documentation of each hook to see whether an
argument can be used to transfer data back to the server.
- 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
358
underlying object is altered through that pointer, the change will be
359 360 361 362 363 364 365
reflected in the server even if no call is made to setArgument.

In all cases, consult the documentation for the particular hook to see whether
parameters can be modified.  As a general rule:

- Do not alter arguments unless you mean the change to be reflected in
the server.
366 367
- If you alter an argument, call @c CalloutHandle::setArgument to update the
value in the @c CalloutHandle object.
368

369
@subsubsection hooksdgNextStep The Next step status
370

371
Note: This functionality used to be provided in Kea 0.9.2 and earlier using
Francis Dupont's avatar
Francis Dupont committed
372
boolean skip flag. See @ref hooksdgSkipFlag for explanation and tips how
373
to migrate your hooks code to this new API.
374 375 376 377 378 379

When a to callouts attached to a hook returns, the server will usually continue
its processing.  However, a callout might have done something that means that
the server should follow another path.  Possible actions a server could take
include:

380 381 382 383 384
- Continue as usual. This is the default value. Unless callouts explicitly
change the status, the server will continue processing. There is no need
to set the status, unless one callout wants to override the status set
by another callout. This action is represented by CalloutHandle::NEXT_STEP_CONTINUE.

385 386 387 388
- Skip the next stage of processing because the callout has already
done it.  For example, a hook is located just before the DHCP server
allocates an address to the client.  A callout may decide to allocate
special addresses for certain clients, in which case it needs to tell
389 390 391
the server not to allocate an address in this case. This action is
hook specific and is represented by CalloutHandle::NEXT_STEP_SKIP.

392
- Drop the packet and continue with the next request. A possible scenario
393 394
is a server where a callout inspects the hardware address of the client
sending the packet and compares it against a black list; if the address
395 396
is on it, the callout notifies the server to drop the packet. This
action is represented by CalloutHandle::NEXT_STEP_DROP.
397

398 399 400 401 402 403
To handle these common cases, the @c CalloutHandle has a setStatus method.
This is set by a callout when it wishes the server to change the normal
processing. Exact meaning is hook specific. Please consult hook API
documentation for details. For historic reasons (Kea 0.9.2 used a single
boolean flag called skip that also doubled in some cases as an indicator
to drop the packet) several hooks use SKIP status to drop the packet.
404 405 406 407 408

The methods to get and set the "skip" flag are getSkip and setSkip. Their
usage is intuitive:

@code
409 410 411 412 413 414
    // Get the current setting of the next step status.
    CalloutHandle::NextStepStatus status = handle.getStatus();

    if (status == CalloutHandle::NEXT_STEP_DROP)
       // Do something...
       :
415 416 417 418 419 420

    // Do some processing...
        :
    if (lease_allocated) {
        // Flag the server to skip the next step of the processing as we
        // already have an address.
421
        handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
422 423
    }
    return;
424

425 426
@endcode

427
Like arguments, the next step status is passed to all callouts on a hook.  Callouts
428 429
later in the list are able to examine (and modify) the settings of earlier ones.

430 431 432 433 434 435 436
@subsubsection hooksdgSkipFlag The "Skip" Flag (deprecated)

In releases 0.9.2 and earlier, the functionality currently offered by next step
status (see @ref hooksdgNextStep) was provided by
a boolean flag called "Skip". However, since it only allowed to either continue
or skip the next processing step and was not extensible to other decisions,
setSkip(bool) call was replaced with a setStatus(enum) in Kea 1.0. This
437
new approach is extensible. If we decide to add new results (e.g., WAIT
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
or RATELIMIT), we will be able to do so without changing the API again.

If you have your hooks libraries that take advantage of skip flag, migrating
to the next step status is very easy. See @ref hooksdgNextStep for detailed
explanation of the new status field.

To migrate, replace this old code:
@code
handle.setSkip(false); // This is the default.

handle.setSkip(true);  // Tell the server to skip the next processing step.

bool skip = hangle.getSkip(); // Check the skip flag state.
if (skip) {
   ...
}
@endcode

with this:

@code

// This is the default.
handle.setStatus(CalloutHandle::NEXT_STEP_CONTINUE);

// Tell the server to skip the next processing step.
handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);

// Check the status state.
CalloutHandle::NextStepStatus status = handle.getStatus();
if (status == CalloutHandle::NEXT_STEP_SKIP) {
    ...
}
@endcode

473
@subsubsection hooksdgCalloutContext Per-Request Context
474

Francis Dupont's avatar
Francis Dupont committed
475
Although the Kea modules can be characterized as handling a single
476
packet at a time - e.g., the DHCPv4 server receives a DHCPDISCOVER packet,
477
processes it and responds with an DHCPOFFER, this may not always be true.
478 479 480
Future developments may have the server processing multiple packets
simultaneously, or to suspend processing on a packet and resume it at
a later time after other packets have been processed.
481

482
As well as argument information, the @c CalloutHandle object can be used by
483
callouts to attach information to a packet being handled by the server.
484 485
This information (known as "context") is not used by the server: its purpose
is to allow callouts to pass information between one another on a
486
per-packet basis.
487

488 489 490 491 492 493
Context associated with a packet only exists only for the duration of the
processing of that packet: when processing is completed, the context is
destroyed.  A new packet starts with a new (empty) context.  Context is
particularly useful in servers that may be processing multiple packets
simultaneously: callouts can effectively attach data to a packet that
follows the packet around the system.
494 495

Context information is held as name/value pairs in the same way
496 497 498
as arguments, being accessed by the pair of methods @c setContext and
@c getContext.  They have the same restrictions as the @c setArgument and
@c getArgument methods - the type of data retrieved from context must
499 500
<B>exactly</B> match the type of the data set.

501
The example in the next section illustrates their use.
502

503
@subsection hooksdgExampleCallouts Example Callouts
504 505 506 507 508 509

Continuing with the tutorial, the requirements need us to retrieve the
hardware address of the incoming packet, classify it, and write it,
together with the assigned IP address, to a log file.  Although we could
do this in one callout, for this example we'll use two:

510 511
- pkt4_receive - a callout on this hook is invoked when a packet has been
received and has been parsed.  It is passed a single argument, "query4"
512 513 514
which is an isc::dhcp::Pkt4Ptr object, holding a pointer to the
isc::dhcp::Pkt4 object (representing a DHCPv4 packet). We will do the
classification here.
515

516 517 518 519
- pkt4_send - called when a response is just about to be sent back to
the client.  It is passed a single argument "response4".  This is the
point at which the example code will write the hardware and IP addresses
to the log file.
520 521 522 523

The standard for naming callouts is to give them the same name as
the hook.  If this is done, the callouts will be automatically found
by the Hooks system (this is discussed further in section @ref
524
hooksdgCalloutRegistration).  For our example, we will assume this is the
525 526 527 528
case, so the code for the first callout (used to classify the client's
hardware address) is:

@code
529
// pkt_receive4.cc
530 531 532 533 534 535 536 537

#include <hooks/hooks.h>
#include <dhcp/pkt4.h>
#include "library_common.h"

#include <string>

using namespace isc::dhcp;
538
using namespace isc::hooks;
539 540 541 542
using namespace std;

extern "C" {

543 544
// This callout is called at the "pkt4_receive" hook.
int pkt4_receive(CalloutHandle& handle) {
545 546 547 548

    // A pointer to the packet is passed to the callout via a "boost" smart
    // pointer. The include file "pkt4.h" typedefs a pointer to the Pkt4
    // object as Pkt4Ptr.  Retrieve a pointer to the object.
549 550
    Pkt4Ptr query4_ptr;
    handle.getArgument("query4", query4_ptr);
551 552

    // Point to the hardware address.
553
    HWAddrPtr hwaddr_ptr = query4_ptr->getHWAddr();
554 555 556 557 558 559

    // The hardware address is held in a public member variable. We'll classify
    // it as interesting if the sum of all the bytes in it is divisible by 4.
    //  (This is a contrived example after all!)
    long sum = 0;
    for (int i = 0; i < hwaddr_ptr->hwaddr_.size(); ++i) {
560
        sum += hwaddr_ptr->hwaddr_[i];
561 562 563 564 565 566
    }

    // Classify it.
    if (sum % 4 == 0) {
        // Store the text form of the hardware address in the context to pass
        // to the next callout.
567 568
        string hwaddr = hwaddr_ptr->toText();
        handle.setContext("hwaddr", hwaddr);
569 570 571 572
    }

    return (0);
};
573 574

}
575 576
@endcode

577
The "pkt4_receive" callout placed the hardware address of an interesting client in
578 579 580 581
the "hwaddr" context for the packet.  Turning now to the callout that will
write this information to the log file:

@code
582
// pkt4_send.cc
583 584 585 586 587 588 589 590

#include <hooks/hooks.h>
#include <dhcp/pkt4.h>
#include "library_common.h"

#include <string>

using namespace isc::dhcp;
591
using namespace isc::hooks;
592 593 594 595
using namespace std;

extern "C" {

596 597
// This callout is called at the "pkt4_send" hook.
int pkt4_send(CalloutHandle& handle) {
598 599 600 601 602

    // Obtain the hardware address of the "interesting" client.  We have to
    // use a try...catch block here because if the client was not interesting,
    // no information would be set and getArgument would thrown an exception.
    string hwaddr;
603 604
    try {
        handle.getContext("hwaddr", hwaddr);
605

606
        // getContext didn't throw so the client is interesting.  Get a pointer
607 608 609
        // to the reply.
        Pkt4Ptr response4_ptr;
        handle.getArgument("response4", response4_ptr);
610 611

        // Get the string form of the IP address.
612
        string ipaddr = response4_ptr->getYiaddr().toText();
613 614 615 616 617 618 619 620

        // Write the information to the log file.
        interesting << hwaddr << " " << ipaddr << "\n";

        // ... and to guard against a crash, we'll flush the output stream.
        flush(interesting);

    } catch (const NoSuchCalloutContext&) {
621 622 623 624
        // No such element in the per-request context with the name "hwaddr".
        // This means that the request was not an interesting, so do nothing
        // and dismiss the exception.
     }
625 626 627 628

    return (0);
}

629
}
630 631
@endcode

632 633 634 635 636
@subsection hooksdgLogging Logging in the Hooks Library

Hooks libraries take part in the DHCP message processing. They also often
modify the server's behavior by taking responsibility for processing
the DHCP message at certain stages and instructing the server to skip
637 638 639 640
the default processing for that stage. Thus, hooks libraries play an
important role in the DHCP server operation and, depending on their
purpose, they may have high complexity, which increases likehood of the
defects in the libraries.
641

642
All hooks libraries should use Kea logging system to faciliate diagnostics
643
of the defects in the libraries and issues with the DHCP server's operation.
644 645 646
Even if the issue doesn't originate in the hooks library itself, the use
of the library may uncover issues in the Kea code that only
manifest themselves in some special circumstances.
647 648

Hooks libraries use the Kea logging system in the same way as any other
649 650
standard Kea library. A hooks library should have at least one logger
defined, but may have multiple loggers if it is desired
651 652
to separate log messages from different functional parts of the library.

653 654 655
Assuming that it has been decided to use logging in the hooks library, the
implementor must select a unique name for the logger. Ideally the name
should have some relationship with the name of the library so that it is
656 657 658
easy to distinguish messages logged from this library. For example,
if the hooks library is used to capture incoming and outgoing DHCP
messages, and the name of the library is "libkea-packet-capture",
659
a suitable logger name could be "packet-capture".
660

661 662
In order to use a logger within the library, the logger should be declared
in a header file, which must be included in all files using
663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
the logger:

@code
#ifndef PACKET_CAPTURE_LOG_H
#define PACKET_CAPTURE_LOG_H

#include <log/message_initializer.h>
#include <log/macros.h>
#include <user_chk_messages.h>

namespace packet_capture {

extern isc::log::Logger packet_capture_logger;

}

#endif
@endcode

682 683
The logger should be defined and initialized in the implementation file,
as illustrated below:
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700

@code
#include <packet_capture_log.h>

namespace packet_capture {

isc::log::Logger packet_capture_logger("packet-capture");

}
@endcode

These files may contain multiple logger declarations and initializations
when the use of more than one logger is desired.

The next step is to add the appropriate message file as described in the
@ref logMessageFiles.

701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
The implementor must make sure that log messages appear in the right
places and that they are logged at the appropriate level. The choice
of the place where the message should appear is not always obvious:
it depends if the particular function being called already logs enough
information and whether adding log message before and/or after the
call to this function would simply duplicate some messages. Sometimes
the choice whether the log message should appear within the function or
outside of it depends on the level of details available for logging. For
example, in many cases it is desirable to include the client identifier
or transaction id of the DHCP packet being processed in logging message.
If this information is available at the higher level but not in the
function being called, it is often better to place the log message at
higher level.  However, the function parameters list could be extended
to include the additional information, and to be logged and the logging
call made from within the function.
716 717

Ideally, the hooks library should contain debug log messages (traces)
718
in all significant decision points in the code, with the information as to
719 720 721
how the code hit this decision point, how it will proceed and why.
However, care should be taken when selecting the log level for those
messages, because selecting too high logging level may impact the
722
performance of the system. For this reason, traces (messages of
723 724 725 726 727 728 729 730 731 732 733 734 735
the debug severity) should use different debug levels for the
messages of different importance or having different performance
requirements to generate the log message. For example, generation of
a log message, which prints full details of a packet, usually requires
more CPU bandwith than the generation of the message which only prints
the packet type and length. Thus, the former should be logged at
lower debug level (see @ref logSeverity for details of using
various debug levels using "dbglevel" parameter).

All loggers defined within the hooks libraries derive the default
configuration from the root logger. For example, when the hooks
library is attached to the DHCPv4 server, the root logger name is
"kea-dhcp4", and the library by default uses configuration of this
736
logger. The configuration of the library's logger can
737
be modified by adding a configuration entry for it
738
to the configuration file. In case of the "packet-capture"
739 740 741 742 743
logger declared above, the full name of the logger in the
configuration file will be "kea-dhcp4.packet-capture". The
configuration specified for this logger will override the default
configuration derived from the root logger.

744
@subsection hooksdgBuild Building the Library
745

Francis Dupont's avatar
Francis Dupont committed
746 747
Building the code requires building a sharable library.  This requires
the the code be compiled as position-independent code (using the
748 749
compiler's "-fpic" switch) and linked as a shared library (with the
linker's "-shared" switch).  The build command also needs to point to
750
the Kea include directory and link in the appropriate libraries.
751

752
Assuming that Kea has been installed in the default location, the
753 754 755 756
command line needed to create the library using the Gnu C++ compiler on a
Linux system is:

@code
757
g++ -I <install-dir>/include/kea -L <install-dir>/lib -fpic -shared -o example.so \
758
    load_unload.cc pkt4_receive.cc pkt4_send.cc version.cc \
759
    -lkea-dhcpsrv -lkea-dhcp++ -lkea-hooks -lkea-log -lkea-util -lkea-exceptions
760 761 762
@endcode

Notes:
763 764 765
- Replace "<install-dir>" with the location in which you installed Kea. Unless
you specified the "--prefix" switch on the "configure" command line when
building Kea, it will be installed in the default location, usually /usr/local.
766
- The compilation command and switches required may vary depending on
767 768
your operating system and compiler - consult the relevant documentation
for details.
769
- The list of libraries that need to be included in the command line
770
depends on the functionality used by the hook code and the module to
771 772
which they are attached. Depending on operating system, you may also need
to explicitly list libraries on which the Kea libraries you link against depend.
773

774
@subsection hooksdgConfiguration Configuring the Hooks Library
775

776 777 778 779
The final step is to make the library known to Kea.  The configuration
keywords of all Kea modules to which hooks can be added contain the
"hooks-libraries" element and user libraries are added to this. (The Kea
hooks system can handle multiple libraries - this is discussed below.)
780

781 782 783
To add the example library (assumed to be in /usr/local/lib) to the
DHCPv4 module, it must be listed in the "hooks-libraries" element of the
"Dhcp4" part of the configuration file:
784 785

@code
786 787
"Dhcp4": {
       :
788 789 790 791
    "hooks-libraries": [
        {
            "library": "/usr/local/lib/example.so"
        }
792 793
    ]
        :
794
}
795
@endcode
796
(Note that "hooks" is plural.)
797

798 799 800 801 802 803 804
Each entry in the "hooks-libraries" list is a structure (a "map" in JSON
parlance) that holds the following element:
- library - the name of the library to load.  This must be a string.

@note The syntax of the hooks-libraries configuration element has changed
since kea 0.9.2 (in that version, "hooks-libraries" was just a list of
libraries).  This change is in preparation for the introduction of
805 806 807 808
library-specific parameters, which will be added to Kea in a version after 1.0.

The DHCPv4 server will load the library and execute the callouts each time a
request is received.
809

810 811 812 813
@note All the above assumes that the hooks library will be used with a
version of Kea that is dynamically-linked.  For information regarding
running hooks libraries against a statically-linked Kea, see @ref
hooksdgStaticallyLinkedKea.
814

815
@section hooksdgAdvancedTopics Advanced Topics
816

817
@subsection hooksdgContextCreateDestroy Context Creation and Destruction
818 819 820 821 822 823 824 825 826

As well as the hooks defined by the server, the hooks framework defines
two hooks of its own, "context_create" and "context_destroy".  The first
is called when a request is created in the server, before any of the
server-specific hooks gets called.  It's purpose it to allow a library
to initialize per-request context. The second is called after all
server-defined hooks have been processed, and is to allow a library to
tidy up.

827
As an example, the "pkt4_send" example above required that the code
828 829 830 831
check for an exception being thrown when accessing the "hwaddr" context
item in case it was not set.  An alternative strategy would have been to
provide a callout for the "context_create" hook and set the context item
"hwaddr" to an empty string. Instead of needing to handle an exception,
832
"pkt4_send" would be guaranteed to get something when looking for
833 834
the hwaddr item and so could write or not write the output depending on
the value.
835 836 837 838 839 840

In most cases, "context_destroy" is not needed as the Hooks system
automatically deletes context. An example where it could be required
is where memory has been allocated by a callout during the processing
of a request and a raw pointer to it stored in the context object. On
destruction of the context, that memory will not be automatically
841
released. Freeing in the memory in the "context_destroy" callout will solve
842 843 844
that problem.

Actually, when the context is destroyed, the destructor
845 846 847 848
associated with any objects stored in it are run. Rather than point to
allocated memory with a raw pointer, a better idea would be to point to
it with a boost "smart" pointer and store that pointer in the context.
When the context is destroyed, the smart pointer's destructor is run,
849
which will automatically delete the pointed-to object.
850

851 852 853 854 855
These approaches are illustrated in the following examples.
Here it is assumed that the hooks library is performing some form of
security checking on the packet and needs to maintain information in
a user-specified "SecurityInformation" object. (The details of this
fictitious object are of no concern here.) The object is created in
856 857
the "context_create" callout and used in both the "pkt4_receive" and the
"pkt4_send" callouts.
858

859 860
@code
// Storing information in a "raw" pointer.  Assume that the
861

862 863 864 865
#include <hooks/hooks.h>
   :

extern "C" {
866

867 868 869 870 871 872 873 874 875
// context_create callout - called when the request is created.
int context_create(CalloutHandle& handle) {
    // Create the security information and store it in the context
    // for this packet.
    SecurityInformation* si = new SecurityInformation();
    handle.setContext("security_information", si);
}

// Callouts that use the context
876
int pkt4_receive(CalloutHandle& handle) {
877
    // Retrieve the pointer to the SecurityInformation object
878
    SecurityInformation* si;
879 880 881 882 883 884 885 886 887 888
    handle.getContext("security_information", si);
        :
        :
    // Set the security information
    si->setSomething(...);

    // The pointed-to information has been updated but the pointer has not been
    // altered, so there is no need to call setContext() again.
}

889
int pkt4_send(CalloutHandle& handle) {
890
    // Retrieve the pointer to the SecurityInformation object
891
    SecurityInformation* si;
892 893 894 895 896 897 898 899 900
    handle.getContext("security_information", si);
        :
        :
    // Retrieve security information
    bool active = si->getSomething(...);
        :
}

// Context destruction.  We need to delete the pointed-to SecurityInformation
901
// object because we will lose the pointer to it when the @c CalloutHandle is
902 903 904
// destroyed.
int context_destroy(CalloutHandle& handle) {
    // Retrieve the pointer to the SecurityInformation object
905
    SecurityInformation* si;
906 907 908 909 910 911 912
    handle.getContext("security_information", si);

    // Delete the pointed-to memory.
    delete si;
}
@endcode

913
The requirement for the "context_destroy" callout can be eliminated if
914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934
a Boost shared ptr is used to point to the allocated memory:

@code
// Storing information in a "raw" pointer.  Assume that the

#include <hooks/hooks.h>
#include <boost/shared_ptr.hpp>
   :

extern "C" {

// context_create callout - called when the request is created.

int context_create(CalloutHandle& handle) {
    // Create the security information and store it in the context for this
    // packet.
    boost::shared_ptr<SecurityInformation> si(new SecurityInformation());
    handle.setContext("security_information", si);
}

// Other than the data type, a shared pointer has similar semantics to a "raw"
935
// pointer.  Only the code from "pkt4_receive" is shown here.
936

937
int pkt4_receive(CalloutHandle& handle) {
938 939 940 941 942 943 944 945 946
    // Retrieve the pointer to the SecurityInformation object
    boost::shared_ptr<SecurityInformation> si;
    handle.setContext("security_information", si);
        :
        :
    // Modify the security information
    si->setSomething(...);

    // The pointed-to information has been updated but the pointer has not
Francis Dupont's avatar
Francis Dupont committed
947
    // altered, so there is no need to reset the context.
948 949 950
}

// No context_destroy callout is needed to delete the allocated
951
// SecurityInformation object.  When the @c CalloutHandle is destroyed, the shared
952 953 954 955 956 957 958 959 960 961 962 963 964 965
// pointer object will be destroyed.  If that is the last shared pointer to the
// allocated memory, then it too will be deleted.
@endcode

(Note that a Boost shared pointer - rather than any other Boost smart pointer -
should be used, as the pointer objects are copied within the hooks framework and
only shared pointers have the correct behavior for the copy operation.)


@subsection hooksdgCalloutRegistration Registering Callouts

As briefly mentioned in @ref hooksdgExampleCallouts, the standard is for
callouts in the user library to have the same name as the name of the
hook to which they are being attached.  This convention was followed
966
in the tutorial, e.g.,  the callout that needed to be attached to the
967
"pkt4_receive" hook was named pkt4_receive.
968 969 970 971 972 973

The reason for this convention is that when the library is loaded, the
hook framework automatically searches the library for functions with
the same names as the server hooks.  When it finds one, it attaches it
to the appropriate hook point.  This simplifies the loading process and
bookkeeping required to create a library of callouts.
974 975 976 977

However, the hooks system is flexible in this area: callouts can have
non-standard names, and multiple callouts can be registered on a hook.

978
@subsubsection hooksdgLibraryHandle The LibraryHandle Object
979 980 981 982 983 984

The way into the part of the hooks framework that allows callout
registration is through the LibraryHandle object.  This was briefly
introduced in the discussion of the framework functions, in that
an object of this type is pass to the "load" function.  A LibraryHandle
can also be obtained from within a callout by calling the CalloutHandle's
985
@c getLibraryHandle() method.
986 987 988

The LibraryHandle provides three methods to manipulate callouts:

989 990 991
- @c registerCallout - register a callout on a hook.
- @c deregisterCallout - deregister a callout from a hook.
- @c deregisterAllCallouts - deregister all callouts on a hook.
992 993 994

The following sections cover some of the ways in which these can be used.

995
@subsubsection hooksdgNonstandardCalloutNames Non-Standard Callout Names
996 997 998

The example in the tutorial used standard names for the callouts.  As noted
above, it is possible to use non-standard names.  Suppose, instead of the
999
callout names "pkt4_receive" and "pkt4_send", we had named our callouts
1000 1001 1002 1003 1004 1005 1006 1007 1008
"classify" and "write_data".  The hooks framework would not have registered
these callouts, so we would have needed to do it ourself.  The place to
do this is the "load" framework function, and its code would have had to
been modified to:

@code
int load(LibraryHandle& libhandle) {
    // Register the callouts on the hooks. We assume that a header file
    // declares the "classify" and "write_data" functions.
1009 1010
    libhandle.registerCallout("pkt4_receive", classify);
    libhandle.registerCallout("pkt4_send", write_data);
1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022

    // Open the log file
    interesting.open("/data/clients/interesting.log",
                     std::fstream::out | std::fstream::app);
    return (interesting ? 0 : 1);
}
@endcode

It is possible for a library to contain callouts with both standard and
non-standard names: ones with standard names will be registered automatically,
ones with non-standard names need to be registered manually.

1023
@subsubsection hooksdgMultipleCallouts Multiple Callouts on a Hook
1024

1025
The Kea hooks framework allows multiple callouts to be attached to
1026 1027 1028 1029
a hook point.  Although it is likely to be rare for user code to need to
do this, there may be instances where it make sense.

To register multiple callouts on a hook, just call
1030
@c LibraryHandle::registerCallout multiple times on the same hook, e.g.,
1031 1032

@code
1033 1034
    libhandle.registerCallout("pkt4_receive", classify);
    libhandle.registerCallout("pkt4_receive", write_data);
1035 1036
@endcode

1037
The hooks framework will call the callouts in the order they are
1038
registered.  The same @c CalloutHandle is passed between them, so any
1039 1040
change made to the CalloutHandle's arguments, "skip" flag, or per-request
context by the first is visible to the second.
1041

Francis Dupont's avatar
Francis Dupont committed
1042
@subsubsection hooksdgDynamicRegistration Dynamic Registration and Re-registration of Callouts
1043 1044 1045

The previous sections have dealt with callouts being registered during
the call to "load".  The hooks framework is more flexible than that
1046
in that callouts can be registered and deregistered within a callout.
1047 1048 1049 1050 1051 1052
In fact, a callout is able to register or deregister itself, and a callout
is able to be registered on a hook multiple times.

Using our contrived example again, the DHCPv4 server processes one request
to completion before it starts processing the next.  With this knowledge,
we could alter the logic of the code so that the callout attached to the
1053
"pkt4_receive" hook registers the callout doing the logging when it detects
1054 1055 1056 1057 1058
an interesting packet, and the callout doing the logging deregisters
itself in its execution.  The relevant modifications to the code in
the tutorial are shown below:

@code
1059
// pkt4_receive.cc
1060 1061
//      :

1062
int pkt4_receive(CalloutHandle& handle) {
1063 1064 1065 1066 1067 1068

            :
            :

    // Classify it.
    if (sum % 4 == 0) {
1069 1070 1071 1072 1073
        // Store the text form of the hardware address in the context to pass
        // to the next callout.
        handle.setContext("hwaddr", hwaddr_ptr->hwaddr_.toText());

        // Register the callback to log the data.
1074
        handle.getLibraryHandle().registerCallout("pkt4_send", write_data);
1075 1076 1077 1078 1079 1080 1081
    }

    return (0);
};
@endcode

@code
1082
// pkt4_send.cc
1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105
        :

int write_data(CalloutHandle& handle) {

    // Obtain the hardware address of the "interesting" client. As the
    // callback is only registered when interesting data is present, we
    // know that the context contains the hardware address so an exception
    // will not be thrown when we call getArgument().
    string hwaddr;
    handle.getArgument("hwaddr", hwaddr);

    // The pointer to the reply.
    ConstPkt4Ptr reply;
    handle.getArgument("reply", reply);

    // Get the string form of the IP address.
    string ipaddr = reply->getYiaddr().toText():

    // Write the information to the log file and flush.
    interesting << hwaddr << " " << ipaddr << "\n";
    flush(interesting);

    // We've logged the data, so deregister ourself.  This callout will not
1106
    // be called again until it is registered by "pkt4_receive".
1107

1108
    handle.getLibraryHandle().deregisterCallout("pkt4_send", write_data);
1109 1110 1111 1112 1113 1114

    return (0);
}
@endcode

Note that the above example used a non-standard name for the callout
1115
that wrote the data.  Had the name been a standard one, it would have been
1116 1117 1118 1119 1120 1121 1122 1123 1124
registered when the library was loaded and called for the first request,
regardless of whether that was defined as "interesting".  (Although as
callouts with standard names are always registered before "load" gets called,
we could have got round that problem by deregistering that particular
callout in the "load" function.)


@note Deregistration of a callout on the hook that is currently
being called only takes effect when the server next calls the hook.
1125 1126 1127 1128
To illustrate this, suppose the callouts attached to a hook are A, B and C
(in that order), and during execution, A deregisters B and C and adds D.
When callout A returns, B and C will still run.  The next time the server
calls the hook's callouts, A and D will run (in that order).
1129

1130
@subsection hooksdgMultipleLibraries Multiple User Libraries
1131

1132
As alluded to in the section @ref hooksdgConfiguration, Kea can load
1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162
multiple libraries.  The libraries are loaded in the order specified in
the configuration, and the callouts attached to the hooks in the order
presented by the libraries.

The following picture illustrates this, and also illustrates the scope of
data passed around the system.

@image html DataScopeArgument.png "Scope of Arguments"

In this illustration, a server has three hook points, alpha, beta
and gamma.  Two libraries are configured, library 1 and library 2.
Library 1 registers the callout "authorize" for hook alpha, "check" for
hook beta and "add_option" for hook gamma.  Library 2 registers "logpkt",
"validate" and "putopt"

The horizontal red lines represent arguments to callouts.  When the server
calls hook alpha, it creates an argument list and calls the
first callout for the hook, "authorize".  When that callout returns, the
same (but possibly modified) argument list is passed to the next callout
in the chain, "logpkt".  Another, separate argument list is created for
hook beta and passed to the callouts "check" and "validate" in
that order.  A similar sequence occurs for hook gamma.

The next picture shows the scope of the context associated with a
request.

@image html DataScopeContext.png "Illustration of per-library context"

The vertical blue lines represent callout context. Context is
per-packet but also per-library.  When the server calls "authorize",
1163
the CalloutHandle's @c getContext and @c setContext methods access a context
1164 1165 1166 1167 1168 1169 1170 1171 1172 1173
created purely for library 1. The next callout on the hook will access
context created for library 2. These contexts are passed to the callouts
associated with the next hook.  So when "check" is called, it gets the
context data that was set by "authorize", when "validate" is called,
it gets the context data set by "logpkt".

It is stressed that the context for callouts associated with different
libraries is entirely separate.  For example, suppose "authorize" sets
the CalloutHandle's context item "foo" to 2 and "logpkt" sets an item of
the same name to the string "bar".  When "check" accesses the context
1174
item "foo", it gets a value of 2; when "validate" accesses an item of
1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186
the same name, it gets the value "bar".

It is also stressed that all this context exists only for the life of the
request being processed.  When that request is complete, all the
context associated with that request - for all libraries - is destroyed,
and new context created for the next request.

This structure means that library authors can use per-request context
without worrying about the presence of other libraries.  Other libraries
may be present, but will not affect the context values set by a library's
callouts.

1187
Configuring multiple libraries just requires listing the libraries
1188
as separate elements of the hooks-libraries configuration element, e.g.,
1189 1190 1191 1192

@code
"Dhcp4": {
       :
1193 1194 1195 1196 1197 1198 1199
    "hooks-libraries": [
        {
            "library": "/usr/lib/library1.so"
        },
        {
            "library": "/opt/library2.so"
        }