diff options
Diffstat (limited to 'src/lib/hooks/hooks_user.dox')
-rw-r--r-- | src/lib/hooks/hooks_user.dox | 1670 |
1 files changed, 1670 insertions, 0 deletions
diff --git a/src/lib/hooks/hooks_user.dox b/src/lib/hooks/hooks_user.dox new file mode 100644 index 0000000..2a51b93 --- /dev/null +++ b/src/lib/hooks/hooks_user.dox @@ -0,0 +1,1670 @@ +// Copyright (C) 2013-2021 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 "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. + +/** + @page hooksdgDevelopersGuide Hooks Developer's Guide + + @section hooksdgIntroduction Introduction + +Although the Kea framework and its DHCP programs +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. + +Since the Kea source code is freely available (Kea being an +open-source project), one option is to modify it to do what +you want. Whilst perfectly feasible, there are drawbacks: + +- Although well-documented, Kea is a large program. Just +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 +behave oddly or to stop working altogether. + +- The change may need to be re-applied or re-written with every new +version of Kea. As new functionality is added or bugs are fixed, +the code or algorithms in the core software may change - and may change +significantly. + +To overcome these problems, Kea provides the "Hooks" interface - +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 +("hook points") Kea will make a call to this code. The call passes +data that the user code can examine and, if required, modify. +Kea uses the modified data in the remainder of its processing. + +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. + +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 +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. + +@note Although the defined interface should not change, the internals +of some of the classes and structures referenced by the user code may +change between versions of Kea. These changes have to be reflected +in the compiled version of the software, hence the need for a rebuild. + + +@subsection hooksdgLanguages Languages + +The core of Kea is written in C++. While it is the intention to +provide interfaces into user code written in other languages, the initial +versions of the Hooks system required that user code be written in C++. +It is no longer the case and there are examples of hooks written for +instance in Python but this guide does not document how to do that. +All examples in this guide are in C++. + + +@subsection hooksdgTerminology Terminology + +In the remainder of this guide, the following terminology is used: + +- Hook/Hook Point - used interchangeably, this is a point in the code at +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 +attached to it. + +- Callout - a user function called by the server at a hook +point. This is so-named because the server "calls out" to the library +to execute a user function. + +- Framework function - the functions that a user library needs to +supply in order for the hooks framework to load and unload the library. + +- User code/user library - non-Kea code that is compiled into a +shared library and loaded by Kea into its address space. + + +@section hooksdgTutorial Tutorial + +To illustrate how to write code that integrates with Kea, we will +use the following (rather contrived) example: + +<i>The Kea DHCPv4 server is used to allocate IPv4 addresses to clients +(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 +the hardware address and allocated IP address for the clients of interest.</i> + +The following sections describe how to implement these requirements. +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 +user hooks code, not to provide an optimal solution. + + +@subsection hooksdgFrameworkFunctions Framework Functions + +Loading and initializing a library holding user code makes use +of three (user-supplied) functions: + +- version - defines the version of Kea code with which the user-library +is built +- load - called when the library is loaded by the server. +- unload - called when the library is unloaded by the server. +- multi_threading_compatible - defines the compatibility (or not) of +the user-library and a multi-threaded DHCP service. + +Of these, only "version" is mandatory, although in our example, all four +are used. + +@subsubsection hooksdgVersionFunction The "version" Function + +"version" is used by the hooks framework to check that the libraries +it is loading are compatible with the version of Kea being run. +Although the hooks system allows Kea and user code to interface +through a defined API, the relationship is somewhat tight in that the +user code will depend on the internal structures of Kea. If these +change - as they can between Kea releases - and Kea is run with +a version of user code built against an earlier version of Kea, a program +crash could result. + +To guard against this, the "version" function must be provided in every +library. It returns a constant defined in header files of the version +of Kea against which it was built. The hooks framework checks this +for compatibility with the running version of Kea before loading +the library. + +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() { + return (KEA_HOOKS_VERSION); +} + +} +@endcode + +The file "hooks/hooks.h" is specified relative to the Kea libraries +source directory - this is covered later in the section @ref hooksdgBuild. +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 +to the hooks framework. + +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. + +@subsubsection hooksdgLoadUnloadFunctions The "load" and "unload" Functions + +As the names suggest, "load" is called when a library is loaded and +"unload" called when it is unloaded. (It is always guaranteed that +"load" is called: "unload" may not be called in some circumstances, +e.g., if the system shuts down abnormally.) These functions are the +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. + +The example does not make any use callouts with non-standard names. However, +as our design requires that the log file be open while Kea is active +and the library loaded, we'll open the file in the "load" function and close +it in "unload". + +We create two files, one for the file handle declaration: + +@code +// library_common.h + +#ifndef LIBRARY_COMMON_H +#define LIBRARY_COMMON_H + +#include <fstream> + +// "Interesting clients" log file handle declaration. +extern std::fstream interesting; + +#endif // LIBRARY_COMMON_H +@endcode + +... and one to hold the "load" and "unload" functions: + +@code +// load_unload.cc + +#include <hooks/hooks.h> +#include "library_common.h" + +using namespace isc::hooks; + +// "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); +} + +} +@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. +- "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.) +- In the initial version of the hooks framework, it was not possible to pass +any configuration information to the "load" function. The name of the log +file had therefore to be hard-coded as an absolute path name or communicated +to the user code by some other means. +- "load" must return 0 on success and non-zero on error. The hooks framework +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 +with "load", a zero value must be returned on success and a non-zero value +on an error. The hooks framework will record a non-zero status return +as an error in the current Kea log but otherwise ignore it. +- As before, the function definitions are enclosed in 'extern "C"' braces. + +In some cases to restrict the library loading to DHCP servers so it +cannot be loaded by the DDNS server or the Control Agent. +The best way to perform this is to check the process name returned +by @c isc::dhcp::Daemon::getProcName() static / class method declared +in process/daemon.h header against "kea-dhcp4" and "kea-dhcp6" +(other values are "kea-dhcp-ddns", "kea-ctrl-agent" and "kea-netconf"). +If you'd like to check the address family too it is returned in DHCP servers +by isc::dhcp::CfgMgr::instance().getFamily() declared in dhcpsrv/cfgmgr.h +with AF_INET and AF_INET6 values. + +@subsubsection hooksdgMultiThreadingCompatibleFunction The "multi_threading_compatible" function + +"multi_threading_compatible" is used by the hooks framework to check +if the libraries it is loading are compatible with the DHCPv4 or DHCPv6 +server multi-threading configuration. The value 0 means not compatible +and is the default when the function is not implemented. Non 0 values +mean compatible. + +If your code implements it and returns the value 0 it is recommended +to document the reason so someone revisiting the code will not by +accident change the code. + +To be compatible means: +- the code associated with DHCP packet processing callouts e.g. +pkt4_receive or pkt6_send must be thread safe so the multi-threaded +DHCP service can simultaneously call more than once one of these callouts. +- commands registered by a library are not required to be thread safe because +commands are executed by the main thread. Now it is a good idea to make +them thread safe and to document cases where they are not. +- when a library implements a thread safe backend API (e.g. host data +source) the service methods must be thread safe. +- a library which modifies the internal configuration of the server, +e.g. creates or deletes a subnet, it must enter a critical section using +the @c isc::util::MultiThreadingCriticalSection RAII class. + +In the tutorial, we'll put "multi_threading_compatible" in its own file, +multi_threading_compatible.cc. The contents are: + +@code +// multi_threading_compatible.cc + +extern "C" { + +int multi_threading_compatible() { + return (1); +} + +} +@endcode + +and for a command creating a new subnet: + +@code +#include <util/multi_threading_mgr.h> + +int commandHandler(CalloutHandle& handle) { + ... + { + // Enter the critical section. + isc::util::MultiThreadingCriticalSection ct; + <add the subnet> + // Leave the critical section. + } + ... +} +@endcode + +@ref hooksMultiThreading provides more details about thread safety +requirements. + +@subsection hooksdgCallouts Callouts + +Having sorted out the framework, we now come to the functions that +actually do something. These functions are known as "callouts" because +the Kea code "calls out" to them. Each Kea server has a number of +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. + +Before we continue with the example, we'll discuss how arguments are +passed to callouts and information is returned to the server. We will +also discuss how information can be moved between callouts. + +@subsubsection hooksdgCalloutSignature The Callout Signature + +All callouts are declared with the signature: +@code +extern "C" { +int callout(CalloutHandle& handle); +} +@endcode + +(As before, the callout is declared with "C" linkage.) Information is passed +between Kea and the callout through name/value pairs in the @c CalloutHandle +object. The object is also used to pass information between callouts on a +per-request basis. (Both of these concepts are explained below.) + +A callout returns an @c int as a status return. A value of 0 indicates +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 +error information to the Kea logging system. + +@subsubsection hooksdgArguments Callout Arguments + +The @c CalloutHandle object provides two methods to get and set the +arguments passed to the callout. These methods are called (naturally +enough) getArgument and setArgument. Their usage is illustrated by the +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); + + // Call the callouts attached to the hook + ... + + // 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 + +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 server, it should retrieve +the data with @c getArgument, modify it, and call @c setArgument to send +it back. + +There are several 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 server passed +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 +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 each hook +point will detail the data type of each argument. +- 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 +underlying object is altered through that pointer, the change will be +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. +- If you alter an argument, call @c CalloutHandle::setArgument to update the +value in the @c CalloutHandle object. + +@subsubsection hooksdgNextStep The Next step status + +Note: This functionality used to be provided in Kea 0.9.2 and earlier using +boolean skip flag. See @ref hooksdgSkipFlag for explanation and tips how +to migrate your hooks code to this new API. + +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: + +- 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. + +- 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 +the server not to allocate an address in this case. This action is +hook specific and is represented by CalloutHandle::NEXT_STEP_SKIP. + +- Drop the packet and continue with the next request. A possible scenario +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 +is on it, the callout notifies the server to drop the packet. This +action is represented by CalloutHandle::NEXT_STEP_DROP. + +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. + +The methods to get and set the "skip" or "drop" state are getStatus and +setStatus. Their usage is intuitive: + +@code + // Get the current setting of the next step status. + auto status = handle.getStatus(); + + if (status == CalloutHandle::NEXT_STEP_DROP) + // Do something... + : + + if (status == CalloutHandle::NEXT_STEP_SKIP) + // Do something... + : + + // Do some processing... + : + if (lease_allocated) { + // Flag the server to skip the next step of the processing as we + // already have an address. + handle.setStatus(CalloutHandle::NEXT_STEP_SKIP); + } + return; + +@endcode + +Like arguments, the next step status is passed to all callouts on a hook. Callouts +later in the list are able to examine (and modify) the settings of earlier ones. + +If using multiple libraries, when the library wants to drop the current packet, +the DROP status must be used instead of the SKIP status so that the packet +processing ends at that specific hook point. + +It is recommended for all callouts to check the status before doing any +processing. As callouts can modify the status, it is recommended to take good +care when doing so, because this will have impact on all remaining hooks as well. +It is highly recommended to not reset the SKIP or DROP status to CONTINUE, even +though possible, so that the rest of the loaded hooks and the server can check +and perform the proper action. + +Some hook points handle special functionality for the server, like pkt4_receive, +pkt6_receive, which handle unpacking of the received packet, pkt4_send, pkt6_send, +which handle packing of the response packet. + +If the hook handles these actions and sets the next step flag to SKIP, it should +also perform a check for the SKIP flag before anything else. If it is already +set, do not pack/unpack the packet (other library, or even the same library, if +loaded multiple times, has done it already). Some libraries might also need to +throw exceptions in such cases because they need to perform specific actions before +pack/unpack (eg. addOption/delOption before pack action), which have no effect if +pack/unpack action is done previously by some other library. + +@code + // Check if other library has already set SKIP flag and performed unpack + // so that unpack is skipped + if (handle.getStatus() != CalloutHandle::NEXT_STEP_SKIP) { + query->unpack(); + } +@endcode + +@code + // Check the status state. + auto status = handle.getStatus(); + if (status == CalloutHandle::NEXT_STEP_SKIP) { + isc_throw(InvalidOperation, "packet pack already handled"); + } + ... + response->delOption(DEL_OPTION_CODE); + ... + response->addOption(ADD_OPTION_CODE); + ... + response->pack(); +@endcode + +As stated before, the order of loading libraries is critical in achieving the +desired behavior, so please read @ref hooksdgMultipleLibraries when configuring +multiple libraries. + +@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 +new approach is extensible. If we decide to add new results (e.g., WAIT +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. +auto status = handle.getStatus(); +if (status == CalloutHandle::NEXT_STEP_SKIP) { + ... +} +@endcode + +@subsubsection hooksdgCalloutContext Per-Request Context + +Although the Kea modules can be characterized as handling a single +packet at a time - e.g., the DHCPv4 server receives a DHCPDISCOVER packet, +processes it and responds with an DHCPOFFER, this may not always be true. +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. + +As well as argument information, the @c CalloutHandle object can be used by +callouts to attach information to a packet being handled by the server. +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 +per-packet basis. + +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. + +Context information is held as name/value pairs in the same way +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 +<B>exactly</B> match the type of the data set. + +The example in the next section illustrates their use. + + +@subsection hooksdgExampleCallouts Example Callouts + +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: + +- 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" +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. + +- 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. + +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 +hooksdgCalloutRegistration). For our example, we will assume this is the +case, so the code for the first callout (used to classify the client's +hardware address) is: + +@code +// pkt_receive4.cc + +#include <hooks/hooks.h> +#include <dhcp/pkt4.h> +#include "library_common.h" + +#include <string> + +using namespace isc::dhcp; +using namespace isc::hooks; +using namespace std; + +extern "C" { + +// This callout is called at the "pkt4_receive" hook. +int pkt4_receive(CalloutHandle& handle) { + + // 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. + Pkt4Ptr query4_ptr; + handle.getArgument("query4", query4_ptr); + + // Point to the hardware address. + HWAddrPtr hwaddr_ptr = query4_ptr->getHWAddr(); + + // 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) { + sum += hwaddr_ptr->hwaddr_[i]; + } + + // Classify it. + if (sum % 4 == 0) { + // Store the text form of the hardware address in the context to pass + // to the next callout. + string hwaddr = hwaddr_ptr->toText(); + handle.setContext("hwaddr", hwaddr); + } + + return (0); +}; + +} +@endcode + +The "pkt4_receive" callout placed the hardware address of an interesting client in +the "hwaddr" context for the packet. Turning now to the callout that will +write this information to the log file: + +@code +// pkt4_send.cc + +#include <hooks/hooks.h> +#include <dhcp/pkt4.h> +#include "library_common.h" + +#include <string> + +using namespace isc::dhcp; +using namespace isc::hooks; +using namespace std; + +extern "C" { + +// This callout is called at the "pkt4_send" hook. +int pkt4_send(CalloutHandle& handle) { + + // 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; + try { + handle.getContext("hwaddr", hwaddr); + + // getContext didn't throw so the client is interesting. Get a pointer + // to the reply. + Pkt4Ptr response4_ptr; + handle.getArgument("response4", response4_ptr); + + // Get the string form of the IP address. + string ipaddr = response4_ptr->getYiaddr().toText(); + + // 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&) { + // 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. + } + + return (0); +} + +} +@endcode + + +@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 +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 likelihood of the +defects in the libraries. + +All hooks libraries should use Kea logging system to facilitate diagnostics +of the defects in the libraries and issues with the DHCP server's operation. +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. + +Hooks libraries use the Kea logging system in the same way as any other +standard Kea library. A hooks library should have at least one logger +defined, but may have multiple loggers if it is desired +to separate log messages from different functional parts of the library. + +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 +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", +a suitable logger name could be "packet-capture". + +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 +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 + +The logger should be defined and initialized in the implementation file, +as illustrated below: + +@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. + +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. + +Ideally, the hooks library should contain debug log messages (traces) +in all significant decision points in the code, with the information as to +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 +performance of the system. For this reason, traces (messages of +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 bandwidth 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 +logger. The configuration of the library's logger can +be modified by adding a configuration entry for it +to the configuration file. In case of the "packet-capture" +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. + + +@subsection hooksdgBuild Building the Library + +Building the code requires building a sharable library. This requires +the the code be compiled as position-independent code (using the +compiler's "-fpic" switch) and linked as a shared library (with the +linker's "-shared" switch). The build command also needs to point to +the Kea include directory and link in the appropriate libraries. + +Assuming that Kea has been installed in the default location, the +command line needed to create the library using the Gnu C++ compiler on a +Linux system is: + +@code +g++ -I <install-dir>/include/kea -L <install-dir>/lib -fpic -shared -o example.so \ + load_unload.cc pkt4_receive.cc pkt4_send.cc version.cc \ + -lkea-dhcpsrv -lkea-dhcp++ -lkea-hooks -lkea-log -lkea-util -lkea-exceptions +@endcode + +Notes: +- 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. +- The compilation command and switches required may vary depending on +your operating system and compiler - consult the relevant documentation +for details. +- The list of libraries that need to be included in the command line +depends on the functionality used by the hook code and the module to +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. + + +@subsection hooksdgConfiguration Configuring the Hooks Library + +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.) + +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: + +@code +"Dhcp4": { + : + "hooks-libraries": [ + { + "library": "/usr/local/lib/example.so" + } + ] + : +} +@endcode +(Note that "hooks" is plural.) + +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 +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. + +@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. + +@section hooksdgAdvancedTopics Advanced Topics + + +@subsection hooksdgContextCreateDestroy Context Creation and Destruction + +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. + +As an example, the "pkt4_send" example above required that the code +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, +"pkt4_send" would be guaranteed to get something when looking for +the hwaddr item and so could write or not write the output depending on +the value. + +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 +released. Freeing in the memory in the "context_destroy" callout will solve +that problem. + +Actually, when the context is destroyed, the destructor +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, +which will automatically delete the pointed-to object. + +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 +the "context_create" callout and used in both the "pkt4_receive" and the +"pkt4_send" callouts. + +@code +// Storing information in a "raw" pointer. Assume that the + +#include <hooks/hooks.h> + : + +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. + SecurityInformation* si = new SecurityInformation(); + handle.setContext("security_information", si); +} + +// Callouts that use the context +int pkt4_receive(CalloutHandle& handle) { + // Retrieve the pointer to the SecurityInformation object + SecurityInformation* si; + 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. +} + +int pkt4_send(CalloutHandle& handle) { + // Retrieve the pointer to the SecurityInformation object + SecurityInformation* si; + handle.getContext("security_information", si); + : + : + // Retrieve security information + bool active = si->getSomething(...); + : +} + +// Context destruction. We need to delete the pointed-to SecurityInformation +// object because we will lose the pointer to it when the @c CalloutHandle is +// destroyed. +int context_destroy(CalloutHandle& handle) { + // Retrieve the pointer to the SecurityInformation object + SecurityInformation* si; + handle.getContext("security_information", si); + + // Delete the pointed-to memory. + delete si; +} +@endcode + +The requirement for the "context_destroy" callout can be eliminated if +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" +// pointer. Only the code from "pkt4_receive" is shown here. + +int pkt4_receive(CalloutHandle& handle) { + // 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 + // altered, so there is no need to reset the context. +} + +// No context_destroy callout is needed to delete the allocated +// SecurityInformation object. When the @c CalloutHandle is destroyed, the shared +// 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 +in the tutorial, e.g., the callout that needed to be attached to the +"pkt4_receive" hook was named pkt4_receive. + +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. + +However, the hooks system is flexible in this area: callouts can have +non-standard names, and multiple callouts can be registered on a hook. + + +@subsubsection hooksdgLibraryHandle The LibraryHandle Object + +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 +@c getLibraryHandle() method. + +The LibraryHandle provides three methods to manipulate callouts: + +- @c registerCallout - register a callout on a hook. +- @c deregisterCallout - deregister a callout from a hook. +- @c deregisterAllCallouts - deregister all callouts on a hook. + +The following sections cover some of the ways in which these can be used. + +@subsubsection hooksdgNonstandardCalloutNames Non-Standard Callout Names + +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 +callout names "pkt4_receive" and "pkt4_send", we had named our callouts +"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. + libhandle.registerCallout("pkt4_receive", classify); + libhandle.registerCallout("pkt4_send", write_data); + + // 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. + +@subsubsection hooksdgCommandHandlers Using Callouts as Command handlers + +Kea servers natively support a set of control commands to retrieve and update +runtime information, e.g. server configuration, basic statistics etc. In +many cases, however, DHCP deployments require support for additional commands +or the natively supported commands don't exactly fulfill one's requirements. + +Taking advantage of Kea's modularity and hooks framework, it is now possible +to easily extend the pool of supported commands by implementing additional +(non-standard) commands within hook libraries. + +A hook library needs to register command handlers for control commands within +its @c load function as follows: + +@code +int load(LibraryHandle& handle) { + handle.registerCommandCallout("diagnostics-enable", diagnostics_enable); + handle.registerCommandCallout("diagnostics-dump", diagnostics_dump); + return (0); +} +@endcode + +Internally, the @c LibraryHandle associates command handlers @c diagnostics_enable +and @c diagnostics_dump with dedicated hook points. These hook points are +given names after the command names, i.e. "$diagnostics_enable" and +"$diagnostics_dump". The dollar sign before the hook point name indicates +that the hook point is dedicated for a command handler, i.e. is not one of +the standard hook points used by the Kea servers. This is just a naming convention, +usually invisible to the hook library implementation and is mainly aimed at +minimizing a risk of collision between names of the hook points registered with +command handlers and standard hook points. + +Once the hook library is loaded and the command handlers supported by the +library are registered, the Kea servers will be able to recognize that those +specific commands are supported and will dispatch commands with the corresponding +names to the hook library (or multiple hook libraries) for processing. See the +documentation of the @ref isc::config::HookedCommandMgr for more details how +it uses @c HooksManager::commandHandlersPresent to determine if the received +command should be dispatched to a hook library for processing. + +The @c diagnostics_enable and @c diagnostics_dump command +handlers must be implemented within the hook library in analogous way to +regular callouts: + +@code +int diagnostics_enable(CalloutHandle& handle) { + ConstElementPtr response; + + try { + ConstElementPtr command; + handle.getArgument("command", command); + ConstElementPtr args; + static_cast<void>(isc::config::parseCommand(args, command)); + + // ... + // handle command here. + // ... + + response = createAnswer(CONTROL_RESULT_SUCCESS, "successful"); + + } catch (const std::exception& ex) { + response = createAnswer(CONTROL_RESULT_ERROR, ex.what()); + } + + handle.setArgument("response", response); + + return (0); +} +@endcode + +The sample code above retrieves the "command" argument which is always provided. +It represents the control command as sent by the controlling client. It includes +command name and command specific arguments. The generic @ref isc::config::parseCommand +can be used to retrieve arguments included in the command. The callout then interprets +these arguments, takes appropriate action and creates a response to the client. +Care should be taken to catch any non-fatal exceptions that may arise during the callout +that should be reported as a failure to the controlling client. In such case, the response +with @c CONTROL_RESULT_ERROR is returned and the callout should return the value of 0. +The non-zero result should only be returned by the callout in case of fatal errors, i.e. +errors which result in inability to generate a response to the client. If the response +is generated, the command handler must set it as "response" argument prior to return. + +It is uncommon but valid scenario to have multiple hook libraries providing command +handlers for the same command. They are invoked sequentially and each of them +can freely modify a response set by a previous callout. This includes entirely +replacing the response provided by previous callouts, if necessary. + +@subsubsection hooksdgMultipleCallouts Multiple Callouts on a Hook + +The Kea hooks framework allows multiple callouts to be attached to +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 +@c LibraryHandle::registerCallout multiple times on the same hook, e.g., + +@code + libhandle.registerCallout("pkt4_receive", classify); + libhandle.registerCallout("pkt4_receive", write_data); +@endcode + +The hooks framework will call the callouts in the order they are +registered. The same @c CalloutHandle is passed between them, so any +change made to the CalloutHandle's arguments, "skip" flag, or per-request +context by the first is visible to the second. + + +@subsection hooksdgMultipleLibraries Multiple User Libraries + +As alluded to in the section @ref hooksdgConfiguration, Kea can load +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", +the CalloutHandle's @c getContext and @c setContext methods access a context +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 +item "foo", it gets a value of 2; when "validate" accesses an item of +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. + +Configuring multiple libraries just requires listing the libraries +as separate elements of the hooks-libraries configuration element, e.g., + +@code +"Dhcp4": { + : + "hooks-libraries": [ + { + "library": "/usr/lib/library1.so" + }, + { + "library": "/opt/library2.so" + } + : + ] +} +@endcode + + +@subsection hooksdgInterLibraryData Passing Data Between Libraries + +In rare cases, it is possible that one library may want to pass +data to another. This can be done in a limited way by means of the +CalloutHandle's @c setArgument and @c getArgument calls. For example, in the +above diagram, the callout "add_option" can pass a value to "putopt" +by setting a name.value pair in the hook's argument list. "putopt" +would be able to read this, but would not be able to return information +back to "add_option". + +All argument names used by Kea will be a combination of letters +(both upper- and lower-case), digits, hyphens and underscores: no +other characters will be used. As argument names are simple strings, +it is suggested that if such a mechanism be used, the names of the data +values passed between the libraries include a special character such as +the dollar symbol or percent sign. In this way there is no danger that +a name will conflict with any existing or future Kea argument names. + + +@subsection hooksdgStaticallyLinkedKea Running Against a Statically-Linked Kea + +If Kea is built with the --enable-static-link switch (set when +running the "configure" script), no shared Kea libraries are built; +instead, archive libraries are created and Kea is linked to them. +If you create a hooks library also linked against these archive libraries, +when the library is loaded you end up with two copies of the library code, +one in Kea and one in your library. + +To run successfully, your library needs to perform run-time initialization +of the Kea code in your library (something performed by Kea +in the case of shared libraries). To do this, call the function +isc::hooks::hooksStaticLinkInit() as the first statement of the load() +function. (If your library does not include a load() function, you need +to add one.) For example: + +@code +#include <hooks/hooks.h> + +extern "C" { + +int version() { + return (KEA_HOOKS_VERSION); +} + +int load() { + isc::hooks::hooksStaticLinkInit(); + : +} + +// Other callout functions + : + +} +@endcode + + +@subsection hooksdgHooksConfig Configuring Hooks Libraries + +Sometimes it is useful for the hook library to have some configuration parameters. +This capability was introduced in Kea 1.1. This is often convenient to follow +generic Kea configuration approach rather than invent your own configuration +logic. Consider the following example: + +@code +"hooks-libraries": [ + { + "library": "/opt/first.so" + }, + { + "library": "/opt/second.so", + "parameters": { + } + }, + { + "library": "/opt/third.so", + "parameters": { + "mail": "spam@example.com", + "floor": 13, + "debug": false, + "users": [ "alice", "bob", "charlie" ], + "languages": { + "french": "bonjour", + "klingon": "yl'el" + } + } + } +] +@endcode + +This example has three hook libraries configured. The first and second have +no parameters. Note that parameters map is optional, but it's perfectly okay to +specify it as an empty map. The third library is more interesting. It has five +parameters specified. The first one called 'mail' is a string. The second one +is an integer and the third one is boolean. Fourth and fifth parameters are +slightly more complicated as they are a list and a map respectively. JSON +structures can be nested if necessary, e.g., you can have a list, which contains +maps, maps that contain maps that contain other maps etc. Any valid JSON +structure can be represented. One important limitation here is that the top +level "parameters" structure must either be a map or not present at all. + +Those parameters can be accessed in load() method. Passed isc::hooks::LibraryHandle +object has a method called getParameter that returns an instance of +isc::data::ConstElementPtr or null pointer if there was no parameter specified. This pointer +will point to an object derived from isc::data::Element class. For detailed +explanation how to use those objects, see isc::data::Element class. + +Here's a brief overview of how to use those elements: + + - x = getParameter("mail") will return instance of isc::data::StringElement. The content + can be accessed with x->stringValue() and will return std::string. + - x = getParameter("floor") will return an instance of isc::data::IntElement. + The content can be accessed with x->intValue() and will return int. + - x = getParameter("debug") will return an instance of isc::data::BoolElement. + Its value can be accessed with x->boolValue() and will return bool. + - x = getParameter("users") will return an instance of isc::data::ListElement. + Its content can be accessed with the following methods: + x->size(), x->get(index) + - x = getParameter("watch-list") will return an instance of isc::data::MapElement. + Its content can be accessed with the following methods: + x->find("klingon"), x->contains("french"), x->size() + +Keep in mind that the user can structure his config file incorrectly. +Remember to check if the structure has the expected type before using type specific +method. For example calling stringValue on IntElement will throw an exception. +You can do this by calling isc::data::Element::getType. + +Here's an example that obtains all of the parameters specified above. +If you want to get nested elements, Element::get(index) and Element::find(name) +will return ElementPtr, which can be iterated in similar manner. + +@code +int load(LibraryHandle& handle) { + ConstElementPtr mail = handle.getParameter("mail"); + ConstElementPtr floor = handle.getParameter("floor"); + ConstElementPtr debug = handle.getParameter("debug"); + ConstElementPtr users = handle.getParameter("users"); + ConstElementPtr lang = handle.getParameter("languages"); + + // String handling example + if (!mail) { + // Handle missing 'mail' parameter here. + return (1); + } + if (mail->getType() != Element::string) { + // Handle incorrect 'mail' parameter here. + return (1); + } + std::string mail_str = mail->stringValue(); + + // In the following examples safety checks are omitted for clarity. + // Make sure you do it properly similar to mail example above + // or you risk dereferencing null pointer or at least throwing + // an exception! + + // Integer handling example + int floor_num = floor->intValue(); + + // Boolean handling example + bool debug_flag = debug->boolValue(); + + // List handling example + std::cout << "There are " << users->size() << " users defined." << std::endl; + for (int i = 0; i < users->size(); i++) { + ConstElementPtr user = users->get(i); + std::cout << "User " << user->stringValue() << std::endl; + } + + // Map handling example + std::cout << "There are " << lang->size() << " languages defined." << std::endl; + if (lang->contains("french")) { + std::cout << "One of them is French!" << std::endl; + } + ConstElementPtr greeting = lang->find("klingon"); + if (greeting) { + std::cout << "Lt. Worf says " << greeting->stringValue() << std::endl; + } + + // All validation steps were successful. The library has all the parameters + // it needs, so we should report a success. + return (0); +} +@endcode + +A good sources of examples could be unit-tests in file src/lib/cc/tests/data_unittests.cc +which are dedicated to isc::data::Element testing and src/lib/hooks/tests/callout_params_library.cc, +which is an example library used in testing. This library expects exactly 3 parameters: +svalue (which is a string), ivalue (which is an integer) and bvalue (which is a boolean). + +@subsection hooksMemoryManagement Memory Management Considerations for Hooks Writer + +Both Kea server memory space and hook library memory space share a common +address space between the opening of the hook (call to dlopen() as the first +phase of the hook library loading) and the closing of the hook (call to +dlclose() as the last phase of the hook library unloading). There are +pointers between the two memory spaces with at least two bad consequences +when they are not correctly managed: + +- Kea uses shared pointers for its objects. If the hook ownership keeps +ownership of an object, this object will never be destroyed, leading to +a trivial memory leak. Some care is recommended when the hook library +uses a garbage collector to not postpone releases of no longer used +objects. Cycles should be avoided too, for instance using weak pointers. +Of course at the opposite, if a Kea object is needed ownership on, it must +be kept in order to not get a dangling pointer when it will be destroyed +at the end of its last reference lifetime. + +- Kea can take some pointers to the hook library memory space, for instance +when a hook object is registered. If these pointers are not destroyed +before the hook library memory space is unmapped by dlclose() this likely +leads to a crash. + +Communication between Kea code and hook library code is provided by +callout handles. For callout points related to a packet, the callout +handle is associated with the packet allowing to get the same callout handle +for all callout points called during processing of a query. + +Hook libraries are closed i.e. hook library memory spaces are unmapped +only when there is no active callout handles. This enforces a correct +behavior at two conditions: + +- there is no "wild" dangling pointers, for instance no registered +objects. + +- this can happen i.e. the hook library does not keep a shared pointer +to a query packet. + +To allow hook writers to fulfill these two conditions the unload() entry +point is called in the first phase of the unloading process since Kea +version 1.7.10. For instance if the hook library uses the PIMPL code +pattern the unload() entry point must reset the pointer to the +hook library implementation. + +@subsection hooksMultiThreading Multi-Threading Considerations for Hooks Writers + +Multi-threading programming in C++ is not easy. For instance STL containers +do not support simultaneous read and write accesses. Kea is written in C++ +so a priori for all Kea APIs one should never assume thread safety. + +When a hook library is internally multi-threaded, its code and any Kea API +used simultaneously by different threads must be thread safe. To mark +the difference between this and the other thread safety requirement this +is called "generic thread safe". + +When multi-threaded packet processing is enabled, Kea servers perform +some actions by the main thread and packet processing by members of +a thread pool. The multi-threading mode is returned by: +@code +isc::util::MultiThreadingMgr::instance().getMode() +@endcode +When it is false, Kea is single threaded and there is no thread safety +requirement, when it is true, the requirement is named Kea packet processing +thread safe shorten into "Kea thread safe". + +A typical Kea thread safe looks like: +@code +int Foo() { + if (MultiThreadingMgr::instance().getMode()) { + std::lock_guard<std::mutex> lock(mutex_); + return (FooInternal()); + } else { + return (FooInternal()); + } +} +@endcode + +The overhead of mutexes and other synchronization tools is far greater +than a test and branch so it is the recommended way to implement Kea +thread safety. + +When a hook library entry point can be called from a packet processing +thread, typically from a packet processing callout but also when +implementing a lease or host backend API, the entry point code must +be Kea thread safe. If it is not possible the hook library must +be marked as not multi-threading compatible (i.e. return 0 from +multi_threading_compatible). + +At the opposite during (re)configuration including libload command +and config backend, only the main thread runs, so version, load, unload, +multi_threading_compatible, dhcp4_srv_configured, dhcp6_srv_configured, +cb4_updated and cb6_updated have no thread safety requirements. + +Other hook library entry points are called by the main thread: + - io service (io context is recent boost versions) is polled by the main + thread + - external socket callbacks are executed by the main thread + - commands including command_process + +The packet processing threads are not stopped so either the entry +point code is Kea thread safe or it uses a critical section +(@c isc::util::MultiThreadingCriticalSection) to stop the packet +processing threads during the execution of the not Kea thread safe code. +Of course critical sections have an impact on performance so they should +be used only for particular cases where no better choice is available. + +Some Kea APIs were made thread safe mainly because they are used by the +packet processing: + - logging is generic thread safe and even multi process safe i.e. + messages logged into a file or by syslog from multiple processes + do not mix. + - statistics update is Kea thread safe. + - lease and host database backends are Kea thread safe. Note if you need to + perform a direct MySQL or PostgreSQL query you must use the connection pool. + - state model and watched thread are generic thread safe (libkea-util) + - interval timer setup and cancel are generic thread safe (libkea-asiolink) + - parking lots are generic thread safe (libkea-hooks) + - external sockets are generic thread safe (libkea-dhcp++) + - http client is Kea thread safe (libkea-http) + +Some other Kea APIs are intrinsically thread safe because they do not +involve a shared structure so for instance despite of its name the +interface manager send methods are generic thread safe. + +Per library documentation details thread safety to help hooks writers +and to provide an exhaustive list of Kea thread safe APIs: + - @ref utilMTConsiderations + - @ref logMTConsiderations + - @ref asiolinkMTConsiderations + - @ref ccMTConsiderations + - @ref databaseMTConsiderations + - @ref ctrlSocketMTConsiderations + - @ref libdhcpMTConsiderations + - @ref statsMTConsiderations + - @ref yangMTConsiderations + - @ref libdhcp_ddnsMTConsiderations + - @ref dhcpEvalMTConsiderations + - @ref cplMTConsiderations + - @ref dhcpDatabaseBackendsMTConsiderations + - @ref libdhcpsrvMTConsiderations + - @ref httpMTConsiderations + +*/ |