123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089 |
- // Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
- //
- // Permission to use, copy, modify, and/or distribute this software for any
- // purpose with or without fee is hereby granted, provided that the above
- // copyright notice and this permission notice appear in all copies.
- //
- // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
- // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
- // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
- // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- // PERFORMANCE OF THIS SOFTWARE.
- // 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 minimise the interaction between Kea and the user
- code, the latter is built independently of Kea in the form of
- a shared library (or 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 requires that user code be written in C++.
- All examples in this guide are in that language.
- @subsection hooksdgTerminology Terminology
- In the remainder of this guide, the following terminology is used:
- - Hook/Hook Point - used interchageably, 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:
- 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.
- 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 hook 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.
- Of these, only "version" is mandatory, although in our example, all three
- 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 current version of the hooks framework, it is not possible to pass
- any configuration information to the "load" function. The name of the log
- file must therefore be hard-coded as an absolute path name or communicated
- to the user code by some other means.
- - "load" must 0 on success and non-zero on error. The hooks framework
- 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.
- @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 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 "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 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 "getArgument" is used to retrieve data from the
- CalloutHandle, and 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 getArgument, modify it, and call setArgument to send
- it back.
- There are several points to be aware of:
- - the data type of the variable in the call to getArgument must match
- the data type of the variable passed to the corresponding 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 "int" and the callout attempted to retrieve it as a
- "long", an exception would be thrown even though any value that can
- be stored in an "int" will fit into a "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 "char*", an exception will
- be thrown if an attempt is made to retrieve it into a variable of type
- "const char*". (However, if an argument is set as a "const int", it can
- be retrieved into an "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 CalloutHandle::setArgument to update the
- value in the CalloutHandle object.
- @subsubsection hooksdgSkipFlag The "Skip" Flag
- 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:
- - 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.
- - 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.
- To handle these common cases, the CalloutHandle has a "skip" flag.
- This is set by a callout when it wishes the server to skip normal
- processing. It is set false by the hooks framework before callouts on a
- hook are called. If the flag is set on return, the server will take the
- "skip" action relevant for the hook.
- The methods to get and set the "skip" flag are getSkip and setSkip. Their
- usage is intuitive:
- @code
- // Get the current setting of the skip flag.
- bool skip = handle.getSkip();
- // Do some processing...
- :
- if (lease_allocated) {
- // Flag the server to skip the next step of the processing as we
- // already have an address.
- handle.setSkip(true);
- }
- return;
- @endcode
- Like arguments, the "skip" flag is passed to all callouts on a hook. Callouts
- later in the list are able to examine (and modify) the settings of earlier ones.
- @subsubsection hooksdgCalloutContext Per-Request Context
- Although the Kea modules can be characterised 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 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 setContext and
- getContext. They have the same restrictions as the setArgument and
- 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::Pkt4 object (representing a DHCP v4 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 hooksdgBuild Building the Library
- Building the code requires building a shareable library. This requires
- the the code be compiled as positition-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 /usr/include/kea -L /usr/lib/kea/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:
- - The compilation command and switches required may vary depending on
- your operating system and compiler - consult the relevant documentation
- for details.
- - The values for the "-I" and "-L" switches depend on where you have
- installed Kea.
- - 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": [ "/usr/local/lib/example.so" ]
- :
- }
- @endcode
- (Note that "hooks" is plural.)
- The DHCPv4 server will load the library and execute the callouts each time a
- request is received.
- @note 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 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 theree is no need to reset the context.
- }
- // No context_destroy callout is needed to delete the allocated
- // SecurityInformation object. When the 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
- getLibraryHandle() method.
- The LibraryHandle provides three methods to manipulate callouts:
- - registerCallout - register a callout on a hook.
- - deregisterCallout - deregister a callout from a hook.
- - 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 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
- 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 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.
- @subsubsection hooksdgDynamicRegistration Dynamic Registration and Reregistration of Callouts
- The previous sections have dealt with callouts being registered during
- the call to "load". The hooks framework is more flexible than that
- in that callouts can be registered and deregistered within a callout.
- In fact, a callout is able to register or deregister itself, and a callout
- is able to be registered on a hook multiple times.
- Using our contrived example again, the DHCPv4 server processes one request
- to completion before it starts processing the next. With this knowledge,
- we could alter the logic of the code so that the callout attached to the
- "pkt4_receive" hook registers the callout doing the logging when it detects
- an interesting packet, and the callout doing the logging deregisters
- itself in its execution. The relevant modifications to the code in
- the tutorial are shown below:
- @code
- // pkt4_receive.cc
- // :
- int pkt4_receive(CalloutHandle& handle) {
- :
- :
- // Classify it.
- if (sum % 4 == 0) {
- // Store the text form of the hardware address in the context to pass
- // to the next callout.
- handle.setContext("hwaddr", hwaddr_ptr->hwaddr_.toText());
- // Register the callback to log the data.
- handle.getLibraryHandle().registerCallout("pkt4_send", write_data);
- }
- return (0);
- };
- @endcode
- @code
- // pkt4_send.cc
- :
- int write_data(CalloutHandle& handle) {
- // Obtain the hardware address of the "interesting" client. As the
- // callback is only registered when interesting data is present, we
- // know that the context contains the hardware address so an exception
- // will not be thrown when we call getArgument().
- string hwaddr;
- handle.getArgument("hwaddr", hwaddr);
- // The pointer to the reply.
- ConstPkt4Ptr reply;
- handle.getArgument("reply", reply);
- // Get the string form of the IP address.
- string ipaddr = reply->getYiaddr().toText():
- // Write the information to the log file and flush.
- interesting << hwaddr << " " << ipaddr << "\n";
- flush(interesting);
- // We've logged the data, so deregister ourself. This callout will not
- // be called again until it is registered by pkt4_receive.
- handle.getLibraryHandle().deregisterCallout("pkt4_send", write_data);
- return (0);
- }
- @endcode
- Note that the above example used a non-standard name for the callout
- that wrote the data. Had the name been a standard one, it would have been
- registered when the library was loaded and called for the first request,
- regardless of whether that was defined as "interesting". (Although as
- callouts with standard names are always registered before "load" gets called,
- we could have got round that problem by deregistering that particular
- callout in the "load" function.)
- @note Deregistration of a callout on the hook that is currently
- being called only takes effect when the server next calls the hook.
- To illustrate this, suppose the callouts attached to a hook are A, B and C
- (in that order), and during execution, A deregisters B and C and adds D.
- When callout A returns, B and C will still run. The next time the server
- calls the hook's callouts, A and D will run (in that order).
- @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 getContext and 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": [ "/usr/lib/library1.so", "/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 setArgument and 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 hooksdgRegisterMultipleLibraries Dynamic Callout Registration and Multiple Libraries
- On a particular hook, callouts are called in the order the libraries appear
- in the configuration and, within a library, in the order the callouts
- are registered.
- This order applies to dynamically-registered callouts as well. As an
- example, consider the diagram above where for hook "beta", callout "check"
- is followed by callout "validate". Suppose that when "authorize" is run,
- it registers a new callout ("double_check") on hook "beta". That
- callout will be inserted at the end of the callouts registered by
- library 1 and before any registered by library 2. It would therefore
- appear between "check" and "validate". On the other hand, if it were
- "logpkt" that registered the new callout, "double_check" would appear
- after "validate".
- @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 (BIND10_HOOKS_VERSION);
- }
- int load() {
- isc::hooks::hooksStaticLinkInit();
- :
- }
- // Other callout functions
- :
- }
- @endcode
- */
|