summaryrefslogtreecommitdiffstats
path: root/src/lib/hooks/hooks_component_developer.dox
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/hooks/hooks_component_developer.dox')
-rw-r--r--src/lib/hooks/hooks_component_developer.dox520
1 files changed, 520 insertions, 0 deletions
diff --git a/src/lib/hooks/hooks_component_developer.dox b/src/lib/hooks/hooks_component_developer.dox
new file mode 100644
index 0000000..baeb5ef
--- /dev/null
+++ b/src/lib/hooks/hooks_component_developer.dox
@@ -0,0 +1,520 @@
+// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/**
+@page hooksComponentDeveloperGuide Guide to Hooks for the Kea Component Developer
+
+@section hooksComponentIntroduction Introduction
+
+The hooks framework is a Kea system that simplifies the way that
+users can write code to modify the behavior of Kea. Instead of
+altering the Kea source code, they write functions that are compiled
+and linked into one or more dynamic shared objects, called here (for
+historical reasons), shared libraries. The library is specified in the Kea
+configuration and at run time Kea dynamically loads the library
+into its address space. At various points in the processing, the component
+"calls out" to functions in the library, passing to them the data is it
+currently working on. They can examine and modify the data as required.
+
+This guide is aimed at Kea developers who want to write or modify a
+Kea component to use hooks. It shows how the component should be written
+to load a shared library at run-time and how to call functions in it.
+
+For information about writing a hooks library containing functions called by Kea
+during its execution, see the document @ref hooksdgDevelopersGuide.
+
+
+@subsection hooksComponentTerminology Terminology
+
+In the remainder of this guide, the following terminology is used:
+
+- Component - a Kea process, e.g. the DHCPv4 or DHCPv6 server.
+
+- Hook/Hook Point - used interchangeably, this is a point in the code at
+which a call to user-written functions is made. Each hook has a name and
+each hook can have any number (including 0) of user-written functions
+attached to it.
+
+- Callout - a user-written function called by the component at a hook
+point. This is so-named because the component "calls out" to the library
+to execute a user-written function.
+
+- User code/user library - non-Kea code that is compiled into a
+shared library and loaded by Kea into its address space. Multiple
+user libraries can be loaded at the same time, each containing callouts for
+the same hooks. The hooks framework calls these libraries one after the
+other. (See the document @ref hooksdgDevelopersGuide for more details.)
+
+
+@subsection hooksComponentLanguages Languages
+
+The core of Kea is written in C++ with some remaining legacy parts in Python.
+While it is the intention to provide the hooks framework for all languages,
+the initial version is for C++. All examples in this guide are in that language.
+
+
+@section hooksComponentBasicIdeas Basic Ideas
+
+From the point of view of the component author, the basic ideas of the hooks
+framework are quite simple:
+
+- The location of hook points in the code need to be determined.
+
+- Name the hook points and register them.
+
+- At each hook point, the component needs to complete the following steps to
+ execute callouts registered by the user-library:
+ -# copy data into the object used to pass information to the callout.
+ -# call the callout.
+ -# copy data back from the object used to exchange information.
+ -# take action based on information returned.
+
+Of course, to set up the system the libraries need to be loaded in the first
+place. The component also needs to:
+
+- Define the configuration item that specifies the user libraries for this
+component.
+
+- Handle configuration changes and load/unload the user libraries.
+
+The following sections will describe these tasks in more detail.
+
+
+@section hooksComponentDefinition Determining the Hook Points
+
+Before any other action takes place, the location of the hook points
+in the code need to be determined. This, of course, depends on the
+component but, as a general guideline, hook locations should be located
+where a callout is able to obtain useful information from Kea and/or
+affect processing. Typically this means at the start or end of a major
+step in the processing of a request, at a point where either useful
+information can be passed to a callout and/or the callout can affect
+the processing of the component. The latter is achieved in either or both
+of the following ways:
+
+- Setting the nest step status. This is an enum that the callout can set
+ and is a quick way of passing information back to the component. It is used
+ to indicate that the component should perform certain actions. Currently
+ there are three statuses defined: CONTINUE (this is the default, the server
+ is expected to continue as usual), SKIP (the server is expected to skip the
+ next processing step, but otherwise continue as usual) and DROP (the server
+ is expected to drop the packet or request completely. The exact action is up
+ to the component.
+
+- Modifying data passed to it. The component should be prepared to continue
+ processing with the data returned by the callout. It is up to the component
+ author whether the data is validated before being used, but doing so will
+ have performance implications.
+
+
+@section hooksComponentRegistration Naming and Registering the Hooks Points
+
+Once the location of the hook point has been determined, it should be
+given a name. This name should be unique amongst all hook points and is
+subject to certain restrictions (see below).
+
+Before the callouts at any hook point are called and any user libraries
+loaded - so typically during component initialization - the component must
+register the names of all the hooks. The registration is done using
+the static method @c isc::hooks::HooksManager::registerHook():
+
+@code
+
+#include <hooks/hooks_manager.h>
+ :
+ int example_index = HooksManager::registerHook("lease_allocate");
+@endcode
+
+The name of the hook is passed as the sole argument to the @c registerHook()
+method. The value returned is the index of that hook point and should
+be retained - it is needed to call the callouts attached to that hook.
+
+Note that a hook only needs to be registered once. There is no mechanism for
+unregistering a hook and there is no need to do so.
+
+
+@subsection hooksComponentAutomaticRegistration Automatic Registration of Hooks
+
+In some components, it may be convenient to set up a single initialization
+function that registers all hooks. For others, it may be more convenient
+for each module within the component to perform its own initialization.
+Since the @c isc::hooks::HooksManager object is a singleton and is created when first
+accessed, a useful trick is to automatically register the hooks when
+the module is loaded.
+
+This technique involves declaring an object outside of any execution
+unit in the module. When the module is loaded, the object's constructor
+is run. By placing the hook registration calls in the constructor,
+the hooks in the module are defined at load time, before any function in
+the module is run. The code for such an initialization sequence would
+be similar to:
+
+@code
+#include <hooks/hooks_manager.h>
+
+namespace {
+
+// Declare structure to perform initialization and store the hook indexes.
+//
+struct MyHooks {
+ int pkt_rcvd; // Index of "packet received" hook
+ int pkt_sent; // Index of "packet sent" hook
+
+ // Constructor
+ MyHooks() {
+ pkt_rcvd = HooksManager::registerHook("pkt_rcvd");
+ pkt_sent = HooksManager::registerHook("pkt_sent");
+ }
+};
+
+// Declare a "MyHooks" object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+MyHooks my_hooks;
+
+} // Anonymous namespace
+
+void Someclass::someFunction() {
+ :
+ // Check if any callouts are defined on the pkt_rcvd hook.
+ if (HooksManager::calloutPresent(my_hooks.pkt_rcvd)) {
+ :
+ }
+ :
+}
+@endcode
+
+
+@subsection hooksComponentHookNames Hook Names
+
+Hook names are strings and in principle, any string can be used as the
+name of a hook, even one containing spaces and non-printable characters.
+However, the following guidelines should be observed:
+
+- The names <b>context_create</b> and <b>context_destroy</b> are reserved to
+the hooks system and are automatically registered: an attempt to register
+one of these will lead to a @c isc::hooks::DuplicateHook exception being thrown.
+
+- The hook name should be a valid "C" function name. If a user gives a
+callout the same name as one of the hooks, the hooks framework will
+automatically load that callout and attach it to the hook: the user does not
+have to explicitly register it.
+
+- The hook name should not conflict with the name of a function in any of
+the system libraries (e.g. naming a hook "sqrt" could lead to the
+square-root function in the system's maths library being attached to the hook
+as a callout).
+
+- Although hook names can be in any case (including mixed case), the Kea
+convention is that they are lower-case.
+
+
+@section hooksComponentCallingCallouts Calling Callouts on a Hook
+
+
+@subsection hooksComponentArgument The Callout Handle
+
+Before describing how to call user code at a hook point, we must first consider
+how to pass data to it.
+
+Each user callout has the signature:
+@code
+int callout_name(isc::hooks::CalloutHandle& handle);
+@endcode
+
+The @c isc::hooks::CalloutHandle object is the object used to pass data to
+and from the callout. This holds the data as a set of name/value pairs,
+each pair being considered an argument to the callout. If there are
+multiple callouts attached to a hook, the @c CalloutHandle is passed to
+each in turn. Should a callout modify an argument, the updated data is
+passed subsequent callouts (each of which could also modify it) before
+being returned to the component.
+
+Two methods are provided to get and set the arguments passed to
+the callout called (naturally enough) @c getArgument and @c setArgument.
+Their usage is illustrated by the following code snippets.
+
+@code
+ int count = 10;
+ boost::shared_ptr<Pkt4> pktptr = ... // Set to appropriate value
+
+ // Assume that "handle_ptr" has been created and is a pointer to a
+ // CalloutHandle.
+ handle_ptr->setArgument("data_count", count);
+ handle_ptr->setArgument("inpacket", pktptr);
+
+ // Call the hook code. lease_assigned_index is the value returned from
+ // HooksManager::registerHook() when the hook was registered.
+ HooksManager::callCallouts(lease_assigned_index, *handle_ptr);
+
+ // Retrieve the modified values
+ handle_ptr->getArgument("data_count", count);
+ handle_ptr->getArgument("inpacket", pktptr);
+@endcode
+
+As can be seen @c getArgument is used to retrieve data from the
+@c CalloutHandle, and @c setArgument used to put data into it. If a callout
+wishes to alter data and pass it back to the component, it should retrieve
+the data with getArgument, modify it, and call setArgument to send
+it back.
+
+There are a couple points to be aware of:
+
+- The data type of the variable in the call to @c getArgument must
+match the data type of the variable passed to the corresponding
+@c setArgument <B>exactly</B>: using what would normally be considered
+to be a "compatible" type is not enough. For example, if the callout
+passed an argument back to the component as an @c int and the component
+attempted to retrieve it as a @c long, an exception would be thrown even
+though any value that can be stored in an @c int will fit into a @c long.
+This restriction also applies the "const" attribute but only as applied to
+data pointed to by pointers, e.g. if an argument is defined as a @c char*,
+an exception will be thrown if an attempt is made to retrieve it into
+a variable of type @c const @c char*. (However, if an argument is set as a
+@c const @c int, it can be retrieved into an @c int.) The documentation of
+a hook point should detail the exact data type of each argument.
+
+- If a pointer to an object is passed to a callout (either a "raw"
+pointer, or a boost smart pointer (as in the example above), and the
+underlying object is altered through that pointer, the change will be
+reflected in the component even if the callout makes no call to setArgument.
+This can be avoided by passing a pointer to a "const" object.
+
+
+@subsection hooksComponentSkipFlag The Skip Flag (obsolete)
+
+
+@subsection hooksComponentNextStep The next step status
+
+Although information is passed back to the component from callouts through
+@c CalloutHandle arguments, a common action for callouts is to inform the component
+that its flow of control should be altered. For example:
+
+- In the DHCP servers, there is a hook at the point at which a lease is
+ about to be assigned. Callouts attached to this hooks may handle the
+ lease assignment in special cases, in which case they set the next step
+ status to SKIP to indicate that the server should not perform lease assignment
+ in this case.
+- A server may define a hook just after a packet is received. A callout
+ attached to the hook might inspect the source address and compare it
+ against a blacklist. If the address is on the list, the callout could set
+ the DROP flag to indicate to the server that the packet should be dropped.
+
+For ease of processing, the @c CalloutHandle contains
+two methods, @c isc::hooks::CalloutHandle::getStatus() and
+@c isc::hooks::CalloutHandle::setStatus(). It is only meaningful for the
+component to use the "get" method. The next step status is cleared (set to
+the default value of CONTINUE) by the hooks framework when the component
+requests that callouts be executed, so any
+value set by the component is lost. Callouts can both inspect the status (it
+might have been set by callouts earlier in the callout list for the hook)
+and set it. Note that the setting of the status by a callout does not
+prevent callouts later in the list from being called: the next step status is
+just an enum value - the only significance comes from its interpretation
+by the component.
+
+An example of use could be:
+@code
+// Set up arguments for DHCP lease assignment.
+handle->setArgument("query", query);
+handle->setArgument("response", response);
+HooksManager::callCallouts(lease_hook_index, *handle_ptr);
+if (handle_ptr->getStatus() != CalloutHandle::NEXT_STEP_SKIP) {
+ // Skip flag not set, do the address allocation
+ :
+}
+@endcode
+
+
+@subsection hooksComponentGettingHandle Getting the Callout Handle
+
+The @c CalloutHandle object is linked to the loaded libraries
+for lifetime reasons (described below). Components
+should retrieve a @c isc::hooks::CalloutHandle using
+@c isc::hooks::HooksManager::createCalloutHandle():
+@code
+ CalloutHandlePtr handle_ptr = HooksManager::createCalloutHandle();
+@endcode
+(@c isc::hooks::CalloutHandlePtr is a typedef for a Boost shared pointer to a
+CalloutHandle.) The CalloutHandle so retrieved may be used for as
+long as the libraries are loaded.
+
+The handle is deleted by resetting the pointer:
+@code
+ handle_ptr.reset();
+@endcode
+... or by letting the handle pointer go out of scope. The actual deletion
+occurs when the CallHandle's reference count goes to zero. (The
+current version of the hooks framework does not maintain any other
+pointer to the returned CalloutHandle, so it gets destroyed when the
+shared pointer to it is cleared or destroyed. However, this may change
+in a future version.)
+
+When the handle is not created locally it is not destroyed so it can
+keep ownership on arguments. In such case the code must call @c
+isc::hooks::CalloutHandle::deleteAllArguments or simply use the RAII
+helper @c isc::hooks::ScopedCalloutHandleState as in:
+@code
+ CalloutHandlePtr handle_ptr = getCalloutHandle(query);
+ ScopedCalloutHandleState state(handle_ptr);
+@endcode
+
+@subsection hooksComponentCallingCallout Calling the Callout
+
+Calling the callout is a simple matter of executing the
+@c isc::hooks::HooksManager::callCallouts() method for the hook index in
+question. For example, with the hook index "pkt_sent" defined as above,
+the hook can be executed by:
+@code
+ HooksManager::callCallouts(pkt_sent, *handle_ptr);
+@endcode
+... where "*handle_ptr" is a reference (note: not a pointer) to the
+@c isc::hooks::CalloutHandle object holding the arguments. No status code
+is returned. If a component needs to get data returned (other than that
+provided by the next step status), it should define an argument through which
+the callout can do so.
+
+@subsubsection hooksComponentConditionalCallout Conditionally Calling Hook Callouts
+
+Most hooks in a component will not have callouts attached to them. To
+avoid the overhead of setting up arguments in the @c CalloutHandle, a
+component can check for callouts before doing that processing using
+@c isc::hooks::HooksManager::calloutsPresent(). Taking the index of a
+hook as its sole argument, the function returns true if there are any
+callouts attached to the hook and false otherwise.
+
+With this check, the code in the component for calling a hook would look
+something like:
+@code
+if (HooksManager::calloutsPresent(lease_hook_index)) {
+ // Set up arguments for lease assignment
+ handle->setArgument("query", query);
+ handle->setArgument("response", response);
+ HooksManager::callCallouts(lease_hook_index, *handle);
+ if (handle->getStatus() != CalloutHandle::NEXT_STEP_DROP) {
+ // Next step allows us to continue, do the address allocation
+ :
+ }
+}
+@endcode
+
+@section hooksComponentLoadLibraries Loading the User Libraries
+
+Once hooks are defined, all the hooks code described above will
+work, even if no libraries are loaded (and even if the library
+loading method is not called). The @c CalloutHandle returned by
+@c isc::hooks::HooksManager::createCalloutHandle() will be valid,
+@c isc::hooks::HooksManager::calloutsPresent() will return false for every
+index, and @c isc::hooks::HooksManager::callCallouts() will be a no-op.
+
+However, if user libraries are specified in the Kea configuration,
+the component should load them. (Note the term "libraries": the hooks
+framework allows multiple user libraries to be loaded.) This should take
+place after the component's configuration has been read, and is achieved
+by the @c isc::hooks::HooksManager::loadLibraries() method. The method is
+passed a vector of strings, each giving the full file specification of
+a user library:
+@code
+ std::vector<std::string> libraries = ... // Get array of libraries
+ bool success = HooksManager::loadLibraries(libraries);
+@endcode
+@c loadLibraries() returns a boolean status which is true if all libraries
+loaded successfully or false if one or more failed to load. Appropriate
+error messages will have been logged in the latter case, the status
+being more to allow the developer to decide whether the execution
+should proceed in such circumstances.
+
+Before @c loadLibraries() can be called a second or subsequent time
+(as a result of a reconfiguration), all existing libraries must be
+successfully unloaded. If a library stays in memory from a programming
+bug in Kea (for instance when no libraries were loaded) or in a
+library (@ref hooksMemoryManagement) @c loadLibraries() throws a not
+recoverable error.
+
+Unloading is done in two phases since Kea version 1.7.10:
+
+- call to @c isc::hooks::HooksManager::prepareUnloadLibraries() which
+calls all unload() entry points and deregisters callout points.
+
+- call to @c isc::hooks::HooksManager::unloadLibraries() even when
+the prepare failed.
+
+If a failure of @c unloadLibraries() is ignored any call to @c loadLibraries()
+will throw.
+
+
+@subsection hooksComponentUnloadIssues Unload and Reload Issues
+
+Unloading a shared library works by unmapping the part of the process's
+virtual address space in which the library lies. This may lead to
+problems if there are still references to that address space elsewhere
+in the process.
+
+In many operating systems, heap storage allowed by a shared library will
+lie in the virtual address allocated to the library. This has implications
+in the hooks framework because:
+
+- Argument information stored in a @c CalloutHandle by a callout in a library
+may lie in the library's address space.
+- Data modified in objects passed as arguments may lie in the address
+space. For example, it is common for a DHCP callout to add "options"
+to a packet: the memory allocated for those options will most likely
+lie in library address space.
+
+The problem really arises because of the extensive use by Kea of boost
+smart pointers. When the pointer is destroyed, the pointed-to memory is
+deallocated. If the pointer points to address space that is unmapped because
+a library has been unloaded, the deletion causes a segmentation fault.
+
+The hooks framework addresses the issue for CalloutHandles by keeping in
+that object a shared pointer to the object controlling library unloading.
+Although a library can be unloaded at any time, it is only when all
+CalloutHandles that could possibly reference address space in the library
+have been deleted that the library will actually be unloaded and the
+address space unmapped.
+
+The hooks framework cannot solve the second issue as the objects in
+question are under control of the component. It is up to the component
+developer to ensure that all such objects have been destroyed before
+libraries are reloaded. In extreme cases this may mean the component
+suspending all processing of incoming requests until all currently
+executing requests have completed and data object destroyed, reloading
+the libraries, then resuming processing.
+
+Since Kea 1.7.10 the unload() entry point is called as the first phase
+of unloading. This gives more chance to hooks writer to perform
+necessary cleanup actions so the second phase, memory unmapping
+can safely happen. The @c isc::hooks::unloadLibraries() function
+was updated too to return false when at least one active callout
+handle remained.
+
+
+@section hooksComponentCallouts Component-Defined Callouts
+
+Previous sections have discussed callout registration by user libraries.
+It is possible for a component to register its own functions (i.e. within
+its own address space) as hook callouts. These functions are called
+in exactly the same way as user callouts, being passed their arguments
+though a CalloutHandle object. (Guidelines for writing callouts can be
+found in @ref hooksdgDevelopersGuide.)
+
+A component can associate with a hook callouts that run either before
+user-registered callouts or after them. Registration is done via a
+@c isc::hooks::LibraryHandle object, a reference to one being obtained
+through the methods @c isc::hooks::HooksManager::preCalloutLibraryHandle()
+(for a handle to register callouts to run before the user library
+callouts) or @c isc::hooks::HooksManager::postCalloutLibraryHandle() (for
+a handle to register callouts to run after the user callouts). Use of
+the @c LibraryHandle to register and deregister callouts is described in
+@ref hooksdgLibraryHandle.
+
+Finally, it should be noted that callouts registered in this way only
+remain registered until the next call to @c isc::hooks::loadLibraries().
+It is up to the component to re-register the callouts after this
+method has been called.
+
+*/