|
@@ -0,0 +1,869 @@
|
|
|
+// 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.
|
|
|
+
|
|
|
+/**
|
|
|
+ @page hookDevelopersGuide Hook Developer's Guide
|
|
|
+
|
|
|
+ @section hookIntroduction 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, adding to development time.
|
|
|
+
|
|
|
+- 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, such code will be referred to
|
|
|
+as "user-written 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 can examine and, if required, modify. BIND 10 used the
|
|
|
+modified code in the remainder of its processing.
|
|
|
+
|
|
|
+In order to minimise the interaction between BIND 10 and the
|
|
|
+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 BIND 10 configuration mechanism means that as
|
|
|
+new versions of BIND 10 are released, there is no need to modify the
|
|
|
+user-written 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-written
|
|
|
+code may alter. These changes will need to be reflected in the compiled
|
|
|
+version of the software, hence the need for a rebuild.
|
|
|
+
|
|
|
+@subsection hookLanguages Languages
|
|
|
+
|
|
|
+The core of BIND 10 is written in C++. While it is the intention to
|
|
|
+provide interfaces into user code written into 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 hookTerminology 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-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 server at a hook
|
|
|
+point. This is so-named because the server "calls out" to the library
|
|
|
+to execute a user-written function.
|
|
|
+
|
|
|
+- Framework function - the functions that a user-written library needs to
|
|
|
+supply in order for the hooks framework for 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 hookTutorial 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 is to illustrate the main features of
|
|
|
+user-written hook code rather than provide an optimal solution.
|
|
|
+
|
|
|
+
|
|
|
+@subsection hookFrameworkFunctions Framework Functions
|
|
|
+
|
|
|
+Loading an initializing a library holding user-written 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 our in out example, all three
|
|
|
+are used.
|
|
|
+
|
|
|
+@subsubsection hookVersionFunction 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-written 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 the version of header
|
|
|
+files against which it was built. The hooks framework checks this for
|
|
|
+compatibility with the running version of BIND 10 before proceeding with
|
|
|
+the library load.
|
|
|
+
|
|
|
+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 hookBuild. 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 hookLoadUnloadFunctions 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 can be registered:
|
|
|
+this is covered further in the section @ref hookCalloutRegistration.
|
|
|
+
|
|
|
+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
|
|
|
+@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, used in the registration
|
|
|
+of functions. As no functions are being called in this example,
|
|
|
+the argument specification omits the variable name (whilst retaining the type)
|
|
|
+to avoid an "unused variable" compiler warning. (The LibraryHandle is
|
|
|
+discussed in the section @ref hookLibraryHandle.)
|
|
|
+- 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-written code by some other means.
|
|
|
+- "load" returns 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 is 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 hookCallouts 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: the purpose of the hooks
|
|
|
+and the data passed to callouts is documented elsewhere.
|
|
|
+
|
|
|
+Before we continue with the example, we'll discuss how arguments are
|
|
|
+passed to callouts and how information can be moved between them.
|
|
|
+
|
|
|
+@subsubsection hookCalloutSignature 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 hookArguments 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 hook code...
|
|
|
+ ...
|
|
|
+
|
|
|
+ // 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 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 hookCalloutContext Per-Request Context
|
|
|
+
|
|
|
+Although many of the BIND 10 modules can be characterised as handling
|
|
|
+a single 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 are effectively
|
|
|
+attaching data to a request and that data 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.
|
|
|
+
|
|
|
+As the example in the tutorial uses per-request context, no separate
|
|
|
+example is given here.
|
|
|
+
|
|
|
+
|
|
|
+@subsection hookExampleCallouts 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 (constant) 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
|
|
|
+hookCalloutRegistration). 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. This is read-only so is passed through a smart pointer
|
|
|
+ // const Pkt4 object. Note that the argument list also contains a
|
|
|
+ // pointer to the query: we don't need to access that in this example.
|
|
|
+ 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.
|
|
|
+ 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". We will do nothing, so just dismiss the exception.
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ return (0);
|
|
|
+}
|
|
|
+
|
|
|
+};
|
|
|
+@endcode
|
|
|
+
|
|
|
+@subsection hookBuild 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 BIND 10 include
|
|
|
+directory and link in the appropriate libraries.
|
|
|
+
|
|
|
+Assuming that BIND 10 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/bind10 -L /usr/lib/bind10 -fpic -shared -o example.so \
|
|
|
+ load_unload.cc pkt_rcvd.cc v4_lease_write.cc version.cc \
|
|
|
+ -lb10-dhcp++ -lb10-util -lb10-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
|
|
|
+BIND 10.
|
|
|
+- 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 (e.g. hook code for DNS will need to link against
|
|
|
+the libb10-dns++ library). Depending on operating system, you may also need
|
|
|
+to explicitly list libraries on which the BIND 10 libraries depend, e.g.
|
|
|
+in the command line above, libb10-exceptions depends on log4cplus, so it
|
|
|
+is possible that "-llog4cplus" may need to be appended to the command line.
|
|
|
+
|
|
|
+@subsection hookConfiguration Configuring the Hook Library
|
|
|
+
|
|
|
+The final step is to make the library known to BIND 10. All BIND 10 modules to
|
|
|
+which hooks can be added contain the "hook_library" element, and user
|
|
|
+libraries are added to this. (The BIND 10 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, the following bindctl commands must be executed:
|
|
|
+
|
|
|
+@code
|
|
|
+> config add Dhcp4/hook_library
|
|
|
+> config set Dhcp4/hook_library[0]/name "/usr/local/lib/example.so"
|
|
|
+> config commit
|
|
|
+@endcode
|
|
|
+
|
|
|
+The DHCPv4 server will load the library and execute the callouts each time a
|
|
|
+request is received.
|
|
|
+
|
|
|
+@section hookAdvancedTopics Advanced Topics
|
|
|
+
|
|
|
+@subsection hookContextCreateDestroy 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 v4_lease_write
|
|
|
+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, v4_lease_write 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.)
|
|
|
+
|
|
|
+
|
|
|
+@subsection hookCalloutRegistration Registering Callouts
|
|
|
+
|
|
|
+As briefly mentioned in @ref hookExampleCallouts, 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 was followed in the tutorial, e.g.
|
|
|
+the callout that needed to be attached to the "pkt_rcvd" hook was named
|
|
|
+pkt_rcvd.
|
|
|
+
|
|
|
+The reason for this standard 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
|
|
|
+that 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 hookLibraryHandle 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 hookNonstandardCalloutNames 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 "pkt_rcvd" and "v4_lease_write", we had named out 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("pkt_rcvd", classify);
|
|
|
+ libhandle.registerCallout("v4_lease_write", 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 hookMultipleCallouts Multiple Callouts on a Hook
|
|
|
+
|
|
|
+The BIND 10 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("pkt_rcvd", classify);
|
|
|
+ libhandle.registerCallout("pkt_rcvd", 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 or per-request context by the first is visible
|
|
|
+to the second.
|
|
|
+
|
|
|
+@subsubsection hookDynamicRegistration 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 and 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
|
|
|
+"pkt_rcvd" 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
|
|
|
+// pkt_rcvd.cc
|
|
|
+// :
|
|
|
+
|
|
|
+int pkt_rcvd(CalloutHandle& handle) {
|
|
|
+
|
|
|
+ :
|
|
|
+ :
|
|
|
+
|
|
|
+ // Classify it.
|
|
|
+ if (sum % 4 == 0) {
|
|
|
+ // Interesting, register the callback to log the data.
|
|
|
+ handle.getLibraryHandle().registerCallout("v4_lease_write", write_data);
|
|
|
+ }
|
|
|
+
|
|
|
+ return (0);
|
|
|
+};
|
|
|
+@endcode
|
|
|
+
|
|
|
+@code
|
|
|
+// v4_lease_write.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 pkt_rcvd.
|
|
|
+
|
|
|
+ handle.getLibraryHandle().deregisterCallout("v4_lease_write", write_data);
|
|
|
+
|
|
|
+ return (0);
|
|
|
+}
|
|
|
+@endcode
|
|
|
+
|
|
|
+Note that the above example used a non-standard name for the callout
|
|
|
+that wronte 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 callouts attached to the hook, callouts
|
|
|
+A and D will run (in that order).
|
|
|
+
|
|
|
+@subsection hookMultipleLibraries Multiple User Libraries
|
|
|
+
|
|
|
+As alluded to in the section @ref hookConfiguration, BIND 10 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.
|
|
|
+
|
|
|
+@subsection hookInterLibraryData 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 BIND 10 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 BIND 10 argument names.
|
|
|
+
|
|
|
+
|
|
|
+@subsection hookRegisterMultipleLibraries 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".
|
|
|
+
|
|
|
+*/
|