Commit 26a805c7 authored by Stephen Morris's avatar Stephen Morris
Browse files

[master] Merge branch 'trac2982'

Conflicts:
	doc/Doxyfile
	doc/devel/mainpage.dox
parents c79031b2 36c92588
......@@ -661,37 +661,37 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
INPUT = ../src/lib/exceptions \
INPUT = ../src/bin/auth \
../src/bin/d2 \
../src/bin/dhcp4 \
../src/bin/dhcp6 \
../src/bin/resolver \
../src/bin/sockcreator \
../src/lib/acl \
../src/lib/asiolink \
../src/lib/bench \
../src/lib/cache \
../src/lib/cc \
../src/lib/config \
../src/lib/cryptolink \
../src/lib/dns \
../src/lib/datasrc \
../src/lib/datasrc/memory \
../src/bin/auth \
../src/bin/resolver \
../src/lib/bench \
../src/lib/dhcp \
../src/lib/dhcp_ddns \
../src/lib/dhcpsrv \
../src/lib/dns \
../src/lib/exceptions \
../src/lib/hooks \
../src/lib/log \
../src/lib/log/compiler \
../src/lib/asiolink/ \
../src/lib/nsas \
../src/lib/testutils \
../src/lib/cache \
../src/lib/server_common/ \
../src/bin/sockcreator/ \
../src/lib/hooks/ \
../src/lib/util/ \
../src/lib/util/io/ \
../src/lib/util/threads/ \
../src/lib/resolve \
../src/lib/acl \
../src/lib/server_common \
../src/lib/statistics \
../src/bin/dhcp6 \
../src/lib/dhcp \
../src/lib/dhcpsrv \
../src/bin/dhcp4 \
../src/lib/dhcp_ddns \
../src/bin/d2 \
../src/lib/testutils \
../src/lib/util \
../src/lib/util/io \
../src/lib/util/threads \
../tests/tools/perfdhcp \
devel
......@@ -778,7 +778,7 @@ EXAMPLE_RECURSIVE = NO
# directories that contain image that are included in the documentation (see
# the \image command).
IMAGE_PATH = ../doc/images
IMAGE_PATH = ../doc/images ../src/lib/hooks/images
# The INPUT_FILTER tag can be used to specify a program that doxygen should
# invoke to filter for each input file. Doxygen will invoke the filter program
......
......@@ -36,9 +36,10 @@
* Regardless of your field of expertise, you are encouraged to visit
* <a href="http://bind10.isc.org/">BIND10 webpage (http://bind10.isc.org)</a>
* @section hooksFramework Hooks Framework
* - @subpage hooksComponentDeveloperGuide
* - @subpage hooksdgDevelopersGuide
* - @subpage dhcpv4Hooks
* - @subpage dhcpv6Hooks
* - @subpage hooksComponentDeveloperGuide
*
* @section dnsMaintenanceGuide DNS Maintenance Guide
* - Authoritative DNS (todo)
......
......@@ -19,7 +19,7 @@
BIND10 features an API (the "Hooks" API) that allows user-written code to
be integrated into BIND 10 and called at specific points in its processing.
An overview of the API and a tutorial for writing such code can be found in
the @ref hooksDevelopersGuide. Information for BIND 10 maintainers can be
the @ref hooksdgDevelopersGuide. Information for BIND 10 maintainers can be
found in the @ref hooksComponentDeveloperGuide.
This manual is more specialised and is aimed at developers of hook
......
......@@ -19,7 +19,7 @@
BIND10 features an API (the "Hooks" API) that allows user-written code to
be integrated into BIND 10 and called at specific points in its processing.
An overview of the API and a tutorial for writing such code can be found in
the @ref hooksDevelopersGuide. Information for BIND 10 maintainers can be
the @ref hooksdgDevelopersGuide. Information for BIND 10 maintainers can be
found in the @ref hooksComponentDeveloperGuide.
This manual is more specialised and is aimed at developers of hook
......
// Copyright (C) 2013 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 Hook Developer's Guide
@section hooksdgIntroduction Introduction
Although the BIND 10 framework and its associated DNS and 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 BIND 10 source code is freely available (BIND 10 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, BIND 10 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 BIND 10. 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, BIND 10 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") BIND 10 will make a call to this code. The call passes
data that the user code can examine and, if required, modify.
BIND 10 uses the modified data in the remainder of its processing.
In order to minimise the interaction between BIND 10 and the user
code, the latter is built independently of BIND 10 in the form of
a shared library (or libraries). These are made known to BIND 10
through its configuration mechanism, and BIND 10 loads the library at
run time. Libraries can be unloaded and reloaded as needed while BIND
10 is running.
Use of a defined API and the BIND 10 configuration mechanism means that
as new versions of BIND 10 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 BIND 10. 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 BIND 10 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-BIND 10 code that is compiled into a
shared library and loaded by BIND 10 into its address space.
@section hooksdgTutorial Tutorial
To illustrate how to write code that integrates with BIND 10, we will
use the following (rather contrived) example:
The BIND 10 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 BIND 10 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 BIND 10 being run.
Although the hooks system allows BIND 10 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 BIND 10. If these
change - as they can between BIND 10 releases - and BIND 10 is run with
a version of user code built against an earlier version of BIND
10, 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 BIND 10 against which it was built. The hooks framework checks this
for compatibility with the running version of BIND 10 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 (BIND10_HOOKS_VERSION);
}
};
@endcode
The file "hooks/hooks.h" is specified relative to the BIND 10 libraries
source directory - this is covered later in the section @ref hooksdgBuild.
It defines the symbol BIND10_HOOKS_VERSION, which has a value that changes
on every release of BIND 10: 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 BIND 10 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"
// "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 BIND 10 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 BIND 10 code "calls out" to them. Each BIND 10 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 BIND 10 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 BIND 10 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 DNS server where a callout inspects the source address of an incoming
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 many of the BIND 10 modules can be characterised as handling
singles packet - e.g. the DHCPv4 server receives a DISCOVER packet,
processes it and responds with an OFFER, this is not true in all cases.
The principal exception is the recursive DNS resolver: this receives a
packet from a client but that packet may itself generate multiple packets
being sent to upstream servers. To avoid possible confusion the rest of
this section uses the term "request" to indicate a request by a client
for some information or action.
As well as argument information, the CalloutHandle object can be used by
callouts to attach information to a request 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-request basis.
Context only exists only for the duration of the request: when a request
is completed, the context is destroyed. A new request starts with no
context information. Context is particularly useful in servers that may
be processing multiple requests simultaneously: callouts can effectively
attach data to a request that follows the request 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:
- pkt_rcvd - a callout on this hook is invoked when a packet has been
received and has been parsed. It is passed a single argument, "query"
which is an isc::dhcp::Pkt4 object (representing a DHCP v4 packet).
We will do the classification here.
- v4_lease_write_post - called when the lease (an assignment of an IPv4
address to a client for a fixed period of time) has been written to the
database. It is passed two arguments, the query ("query")
and the response (called "reply"). 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_rcvd.cc
#include <hooks/hooks.h>
#include <dhcp/pkt4.h>
#include "library_common.h"
#include <string>
using namespace isc::dhcp;
using namespace std;
extern "C" {
// This callout is called at the "pkt_rcvd" hook.
int pkt_rcvd(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 query_ptr;
handle.getArgument("query", query_ptr);
// Point to the hardware address.
HwAddrPtr hwaddr_ptr = query_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->hwadr_[i];
}
// 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());
}
return (0);
};
@endcode
The pct_rcvd 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
// v4_lease_write.cc
#include <hooks/hooks.h>
#include <dhcp/pkt4.h>
#include "library_common.h"
#include <string>
using namespace isc::dhcp;
using namespace std;
extern "C" {
// This callout is called at the "v4_lease_write_post" hook.
int v4_lease_write_post(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.getArgument("hwaddr", hwaddr) {
// getArgument didn't throw so the client is interesting. Get a pointer
// to the reply. Note that the argument list for this hook also
// contains a pointer to the query: we don't need to access that in this