hooks_user.dox 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408
  1. // Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. // Note: the prefix "hooksdg" to all labels is an abbreviation for "Hooks
  7. // Developer's Guide" and is used to prevent a clash with symbols in any
  8. // other Doxygen file.
  9. /**
  10. @page hooksdgDevelopersGuide Hooks Developer's Guide
  11. @section hooksdgIntroduction Introduction
  12. Although the Kea framework and its DHCP programs
  13. provide comprehensive functionality, there will be times when it does
  14. not quite do what you require: the processing has to be extended in some
  15. way to solve your problem.
  16. Since the Kea source code is freely available (Kea being an
  17. open-source project), one option is to modify it to do what
  18. you want. Whilst perfectly feasible, there are drawbacks:
  19. - Although well-documented, Kea is a large program. Just
  20. understanding how it works will take a significant amount of time. In
  21. addition, despite the fact that its object-oriented design keeps the
  22. coupling between modules to a minimum, an inappropriate change to one
  23. part of the program during the extension could cause another to
  24. behave oddly or to stop working altogether.
  25. - The change may need to be re-applied or re-written with every new
  26. version of Kea. As new functionality is added or bugs are fixed,
  27. the code or algorithms in the core software may change - and may change
  28. significantly.
  29. To overcome these problems, Kea provides the "Hooks" interface -
  30. a defined interface for third-party or user-written code. (For ease of
  31. reference in the rest of this document, all such code will be referred
  32. to as "user code".) At specific points in its processing
  33. ("hook points") Kea will make a call to this code. The call passes
  34. data that the user code can examine and, if required, modify.
  35. Kea uses the modified data in the remainder of its processing.
  36. In order to minimize the interaction between Kea and the user code,
  37. the latter is built independently of Kea in the form of one or more
  38. dynamic shared objects, called here (for historical reasons), shared
  39. libraries. These are made known to Kea through its configuration
  40. mechanism, and Kea loads the library at run time. Libraries can be
  41. unloaded and reloaded as needed while Kea is running.
  42. Use of a defined API and the Kea configuration mechanism means that
  43. as new versions of Kea are released, there is no need to modify
  44. the user code. Unless there is a major change in an interface
  45. (which will be clearly documented), all that will be required is a rebuild
  46. of the libraries.
  47. @note Although the defined interface should not change, the internals
  48. of some of the classes and structures referenced by the user code may
  49. change between versions of Kea. These changes have to be reflected
  50. in the compiled version of the software, hence the need for a rebuild.
  51. @subsection hooksdgLanguages Languages
  52. The core of Kea is written in C++. While it is the intention to
  53. provide interfaces into user code written in other languages, the initial
  54. versions of the Hooks system requires that user code be written in C++.
  55. All examples in this guide are in that language.
  56. @subsection hooksdgTerminology Terminology
  57. In the remainder of this guide, the following terminology is used:
  58. - Hook/Hook Point - used interchageably, this is a point in the code at
  59. which a call to user functions is made. Each hook has a name and
  60. each hook can have any number (including 0) of user functions
  61. attached to it.
  62. - Callout - a user function called by the server at a hook
  63. point. This is so-named because the server "calls out" to the library
  64. to execute a user function.
  65. - Framework function - the functions that a user library needs to
  66. supply in order for the hooks framework to load and unload the library.
  67. - User code/user library - non-Kea code that is compiled into a
  68. shared library and loaded by Kea into its address space.
  69. @section hooksdgTutorial Tutorial
  70. To illustrate how to write code that integrates with Kea, we will
  71. use the following (rather contrived) example:
  72. <i>The Kea DHCPv4 server is used to allocate IPv4 addresses to clients
  73. (as well as to pass them other information such as the address of DNS
  74. servers). We will suppose that we need to classify clients requesting
  75. IPv4 addresses according to their hardware address, and want to log both
  76. the hardware address and allocated IP address for the clients of interest.</i>
  77. The following sections describe how to implement these requirements.
  78. The code presented here is not efficient and there are better ways of
  79. doing the task. The aim however, is to illustrate the main features of
  80. user hooks code, not to provide an optimal solution.
  81. @subsection hooksdgFrameworkFunctions Framework Functions
  82. Loading and initializing a library holding user code makes use
  83. of three (user-supplied) functions:
  84. - version - defines the version of Kea code with which the user-library
  85. is built
  86. - load - called when the library is loaded by the server.
  87. - unload - called when the library is unloaded by the server.
  88. Of these, only "version" is mandatory, although in our example, all three
  89. are used.
  90. @subsubsection hooksdgVersionFunction The "version" Function
  91. "version" is used by the hooks framework to check that the libraries
  92. it is loading are compatible with the version of Kea being run.
  93. Although the hooks system allows Kea and user code to interface
  94. through a defined API, the relationship is somewhat tight in that the
  95. user code will depend on the internal structures of Kea. If these
  96. change - as they can between Kea releases - and Kea is run with
  97. a version of user code built against an earlier version of Kea, a program
  98. crash could result.
  99. To guard against this, the "version" function must be provided in every
  100. library. It returns a constant defined in header files of the version
  101. of Kea against which it was built. The hooks framework checks this
  102. for compatibility with the running version of Kea before loading
  103. the library.
  104. In this tutorial, we'll put "version" in its own file, version.cc. The
  105. contents are:
  106. @code
  107. // version.cc
  108. #include <hooks/hooks.h>
  109. extern "C" {
  110. int version() {
  111. return (KEA_HOOKS_VERSION);
  112. }
  113. }
  114. @endcode
  115. The file "hooks/hooks.h" is specified relative to the Kea libraries
  116. source directory - this is covered later in the section @ref hooksdgBuild.
  117. It defines the symbol KEA_HOOKS_VERSION, which has a value that changes
  118. on every release of Kea: this is the value that needs to be returned
  119. to the hooks framework.
  120. A final point to note is that the definition of "version" is enclosed
  121. within 'extern "C"' braces. All functions accessed by the hooks
  122. framework use C linkage, mainly to avoid the name mangling that
  123. accompanies use of the C++ compiler, but also to avoid issues related
  124. to namespaces.
  125. @subsubsection hooksdgLoadUnloadFunctions The "load" and "unload" Functions
  126. As the names suggest, "load" is called when a library is loaded and
  127. "unload" called when it is unloaded. (It is always guaranteed that
  128. "load" is called: "unload" may not be called in some circumstances,
  129. e.g., if the system shuts down abnormally.) These functions are the
  130. places where any library-wide resources are allocated and deallocated.
  131. "load" is also the place where any callouts with non-standard names
  132. (names that are not hook point names) can be registered:
  133. this is covered further in the section @ref hooksdgCalloutRegistration.
  134. The example does not make any use callouts with non-standard names. However,
  135. as our design requires that the log file be open while Kea is active
  136. and the library loaded, we'll open the file in the "load" function and close
  137. it in "unload".
  138. We create two files, one for the file handle declaration:
  139. @code
  140. // library_common.h
  141. #ifndef LIBRARY_COMMON_H
  142. #define LIBRARY_COMMON_H
  143. #include <fstream>
  144. // "Interesting clients" log file handle declaration.
  145. extern std::fstream interesting;
  146. #endif // LIBRARY_COMMON_H
  147. @endcode
  148. ... and one to hold the "load" and "unload" functions:
  149. @code
  150. // load_unload.cc
  151. #include <hooks/hooks.h>
  152. #include "library_common.h"
  153. using namespace isc::hooks;
  154. // "Interesting clients" log file handle definition.
  155. std::fstream interesting;
  156. extern "C" {
  157. int load(LibraryHandle&) {
  158. interesting.open("/data/clients/interesting.log",
  159. std::fstream::out | std::fstream::app);
  160. return (interesting ? 0 : 1);
  161. }
  162. int unload() {
  163. if (interesting) {
  164. interesting.close();
  165. }
  166. return (0);
  167. }
  168. }
  169. @endcode
  170. Notes:
  171. - The file handle ("interesting") is declared in a header file and defined
  172. outside of any function. This means it can be accessed by any function
  173. within the user library. For convenience, the definition is in the
  174. load_unload.cc file.
  175. - "load" is called with a LibraryHandle argument, this being used in
  176. the registration of functions. As no functions are being registered
  177. in this example, the argument specification omits the variable name
  178. (whilst retaining the type) to avoid an "unused variable" compiler
  179. warning. (The LibraryHandle and its use is discussed in the section
  180. @ref hooksdgLibraryHandle.)
  181. - In the current version of the hooks framework, it is not possible to pass
  182. any configuration information to the "load" function. The name of the log
  183. file must therefore be hard-coded as an absolute path name or communicated
  184. to the user code by some other means.
  185. - "load" must 0 on success and non-zero on error. The hooks framework
  186. will abandon the loading of the library if "load" returns an error status.
  187. (In this example, "interesting" can be tested as a boolean value,
  188. returning "true" if the file opened successfully.)
  189. - "unload" closes the log file if it is open and is a no-op otherwise. As
  190. with "load", a zero value must be returned on success and a non-zero value
  191. on an error. The hooks framework will record a non-zero status return
  192. as an error in the current Kea log but otherwise ignore it.
  193. - As before, the function definitions are enclosed in 'extern "C"' braces.
  194. @subsection hooksdgCallouts Callouts
  195. Having sorted out the framework, we now come to the functions that
  196. actually do something. These functions are known as "callouts" because
  197. the Kea code "calls out" to them. Each Kea server has a number of
  198. hooks to which callouts can be attached: server-specific documentation
  199. describes in detail the points in the server at which the hooks are
  200. present together with the data passed to callouts attached to them.
  201. Before we continue with the example, we'll discuss how arguments are
  202. passed to callouts and information is returned to the server. We will
  203. also discuss how information can be moved between callouts.
  204. @subsubsection hooksdgCalloutSignature The Callout Signature
  205. All callouts are declared with the signature:
  206. @code
  207. extern "C" {
  208. int callout(CalloutHandle& handle);
  209. }
  210. @endcode
  211. (As before, the callout is declared with "C" linkage.) Information is passed
  212. between Kea and the callout through name/value pairs in the @c CalloutHandle
  213. object. The object is also used to pass information between callouts on a
  214. per-request basis. (Both of these concepts are explained below.)
  215. A callout returns an @c int as a status return. A value of 0 indicates
  216. success, anything else signifies an error. The status return has no
  217. effect on server processing; the only difference between a success
  218. and error code is that if the latter is returned, the server will
  219. log an error, specifying both the library and hook that generated it.
  220. Effectively the return status provides a quick way for a callout to log
  221. error information to the Kea logging system.
  222. @subsubsection hooksdgArguments Callout Arguments
  223. The @c CalloutHandle object provides two methods to get and set the
  224. arguments passed to the callout. These methods are called (naturally
  225. enough) getArgument and setArgument. Their usage is illustrated by the
  226. following code snippets.
  227. @code
  228. // Server-side code snippet to show the setting of arguments
  229. int count = 10;
  230. boost::shared_ptr<Pkt4> pktptr = ... // Set to appropriate value
  231. // Assume that "handle" has been created
  232. handle.setArgument("data_count", count);
  233. handle.setArgument("inpacket", pktptr);
  234. // Call the callouts attached to the hook
  235. ...
  236. // Retrieve the modified values
  237. handle.getArgument("data_count", count);
  238. handle.getArgument("inpacket", pktptr);
  239. @endcode
  240. In the callout
  241. @code
  242. int number;
  243. boost::shared_ptr<Pkt4> packet;
  244. // Retrieve data set by the server.
  245. handle.getArgument("data_count", number);
  246. handle.getArgument("inpacket", packet);
  247. // Modify "number"
  248. number = ...;
  249. // Update the arguments to send the value back to the server.
  250. handle.setArgument("data_count", number);
  251. @endcode
  252. As can be seen @c getArgument is used to retrieve data from the
  253. @c CalloutHandle, and @c setArgument used to put data into it. If a callout
  254. wishes to alter data and pass it back to the server, it should retrieve
  255. the data with @c getArgument, modify it, and call @c setArgument to send
  256. it back.
  257. There are several points to be aware of:
  258. - the data type of the variable in the call to @c getArgument must match
  259. the data type of the variable passed to the corresponding @c setArgument
  260. <B>exactly</B>: using what would normally be considered to be a
  261. "compatible" type is not enough. For example, if the server passed
  262. an argument as an @c int and the callout attempted to retrieve it as a
  263. @c long, an exception would be thrown even though any value that can
  264. be stored in an @c int will fit into a @c long. This restriction also
  265. applies the "const" attribute but only as applied to data pointed to by
  266. pointers, e.g., if an argument is defined as a @c char*, an exception will
  267. be thrown if an attempt is made to retrieve it into a variable of type
  268. @c const @c char*. (However, if an argument is set as a @c const @c int,
  269. it can be retrieved into an @c int.) The documentation of each hook
  270. point will detail the data type of each argument.
  271. - Although all arguments can be modified, some altered values may not
  272. be read by the server. (These would be ones that the server considers
  273. "read-only".) Consult the documentation of each hook to see whether an
  274. argument can be used to transfer data back to the server.
  275. - If a pointer to an object is passed to a callout (either a "raw"
  276. pointer, or a boost smart pointer (as in the example above), and the
  277. underlying object is altered through that pointer, the change will be
  278. reflected in the server even if no call is made to setArgument.
  279. In all cases, consult the documentation for the particular hook to see whether
  280. parameters can be modified. As a general rule:
  281. - Do not alter arguments unless you mean the change to be reflected in
  282. the server.
  283. - If you alter an argument, call @c CalloutHandle::setArgument to update the
  284. value in the @c CalloutHandle object.
  285. @subsubsection hooksdgNextStep The Next step status
  286. Note: This functionality used to be provided in Kea 0.9.2 and earlier using
  287. boolean skip flag. See @ref hooksdgSkipFlag for explanation and tips how
  288. to migrate your hooks code to this new API.
  289. When a to callouts attached to a hook returns, the server will usually continue
  290. its processing. However, a callout might have done something that means that
  291. the server should follow another path. Possible actions a server could take
  292. include:
  293. - Continue as usual. This is the default value. Unless callouts explicitly
  294. change the status, the server will continue processing. There is no need
  295. to set the status, unless one callout wants to override the status set
  296. by another callout. This action is represented by CalloutHandle::NEXT_STEP_CONTINUE.
  297. - Skip the next stage of processing because the callout has already
  298. done it. For example, a hook is located just before the DHCP server
  299. allocates an address to the client. A callout may decide to allocate
  300. special addresses for certain clients, in which case it needs to tell
  301. the server not to allocate an address in this case. This action is
  302. hook specific and is represented by CalloutHandle::NEXT_STEP_SKIP.
  303. - Drop the packet and continue with the next request. A possible scenario
  304. is a server where a callout inspects the hardware address of the client
  305. sending the packet and compares it against a black list; if the address
  306. is on it, the callout notifies the server to drop the packet. This
  307. action is represented by CalloutHandle::NEXT_STEP_DROP.
  308. To handle these common cases, the @c CalloutHandle has a setStatus method.
  309. This is set by a callout when it wishes the server to change the normal
  310. processing. Exact meaning is hook specific. Please consult hook API
  311. documentation for details. For historic reasons (Kea 0.9.2 used a single
  312. boolean flag called skip that also doubled in some cases as an indicator
  313. to drop the packet) several hooks use SKIP status to drop the packet.
  314. The methods to get and set the "skip" flag are getSkip and setSkip. Their
  315. usage is intuitive:
  316. @code
  317. // Get the current setting of the next step status.
  318. CalloutHandle::NextStepStatus status = handle.getStatus();
  319. if (status == CalloutHandle::NEXT_STEP_DROP)
  320. // Do something...
  321. :
  322. // Do some processing...
  323. :
  324. if (lease_allocated) {
  325. // Flag the server to skip the next step of the processing as we
  326. // already have an address.
  327. handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
  328. }
  329. return;
  330. @endcode
  331. Like arguments, the next step status is passed to all callouts on a hook. Callouts
  332. later in the list are able to examine (and modify) the settings of earlier ones.
  333. @subsubsection hooksdgSkipFlag The "Skip" Flag (deprecated)
  334. In releases 0.9.2 and earlier, the functionality currently offered by next step
  335. status (see @ref hooksdgNextStep) was provided by
  336. a boolean flag called "Skip". However, since it only allowed to either continue
  337. or skip the next processing step and was not extensible to other decisions,
  338. setSkip(bool) call was replaced with a setStatus(enum) in Kea 1.0. This
  339. new approach is extensible. If we decide to add new results (e.g., WAIT
  340. or RATELIMIT), we will be able to do so without changing the API again.
  341. If you have your hooks libraries that take advantage of skip flag, migrating
  342. to the next step status is very easy. See @ref hooksdgNextStep for detailed
  343. explanation of the new status field.
  344. To migrate, replace this old code:
  345. @code
  346. handle.setSkip(false); // This is the default.
  347. handle.setSkip(true); // Tell the server to skip the next processing step.
  348. bool skip = hangle.getSkip(); // Check the skip flag state.
  349. if (skip) {
  350. ...
  351. }
  352. @endcode
  353. with this:
  354. @code
  355. // This is the default.
  356. handle.setStatus(CalloutHandle::NEXT_STEP_CONTINUE);
  357. // Tell the server to skip the next processing step.
  358. handle.setStatus(CalloutHandle::NEXT_STEP_SKIP);
  359. // Check the status state.
  360. CalloutHandle::NextStepStatus status = handle.getStatus();
  361. if (status == CalloutHandle::NEXT_STEP_SKIP) {
  362. ...
  363. }
  364. @endcode
  365. @subsubsection hooksdgCalloutContext Per-Request Context
  366. Although the Kea modules can be characterized as handling a single
  367. packet at a time - e.g., the DHCPv4 server receives a DHCPDISCOVER packet,
  368. processes it and responds with an DHCPOFFER, this may not always be true.
  369. Future developments may have the server processing multiple packets
  370. simultaneously, or to suspend processing on a packet and resume it at
  371. a later time after other packets have been processed.
  372. As well as argument information, the @c CalloutHandle object can be used by
  373. callouts to attach information to a packet being handled by the server.
  374. This information (known as "context") is not used by the server: its purpose
  375. is to allow callouts to pass information between one another on a
  376. per-packet basis.
  377. Context associated with a packet only exists only for the duration of the
  378. processing of that packet: when processing is completed, the context is
  379. destroyed. A new packet starts with a new (empty) context. Context is
  380. particularly useful in servers that may be processing multiple packets
  381. simultaneously: callouts can effectively attach data to a packet that
  382. follows the packet around the system.
  383. Context information is held as name/value pairs in the same way
  384. as arguments, being accessed by the pair of methods @c setContext and
  385. @c getContext. They have the same restrictions as the @c setArgument and
  386. @c getArgument methods - the type of data retrieved from context must
  387. <B>exactly</B> match the type of the data set.
  388. The example in the next section illustrates their use.
  389. @subsection hooksdgExampleCallouts Example Callouts
  390. Continuing with the tutorial, the requirements need us to retrieve the
  391. hardware address of the incoming packet, classify it, and write it,
  392. together with the assigned IP address, to a log file. Although we could
  393. do this in one callout, for this example we'll use two:
  394. - pkt4_receive - a callout on this hook is invoked when a packet has been
  395. received and has been parsed. It is passed a single argument, "query4"
  396. which is an isc::dhcp::Pkt4Ptr object, holding a pointer to the
  397. isc::dhcp::Pkt4 object (representing a DHCPv4 packet). We will do the
  398. classification here.
  399. - pkt4_send - called when a response is just about to be sent back to
  400. the client. It is passed a single argument "response4". This is the
  401. point at which the example code will write the hardware and IP addresses
  402. to the log file.
  403. The standard for naming callouts is to give them the same name as
  404. the hook. If this is done, the callouts will be automatically found
  405. by the Hooks system (this is discussed further in section @ref
  406. hooksdgCalloutRegistration). For our example, we will assume this is the
  407. case, so the code for the first callout (used to classify the client's
  408. hardware address) is:
  409. @code
  410. // pkt_receive4.cc
  411. #include <hooks/hooks.h>
  412. #include <dhcp/pkt4.h>
  413. #include "library_common.h"
  414. #include <string>
  415. using namespace isc::dhcp;
  416. using namespace isc::hooks;
  417. using namespace std;
  418. extern "C" {
  419. // This callout is called at the "pkt4_receive" hook.
  420. int pkt4_receive(CalloutHandle& handle) {
  421. // A pointer to the packet is passed to the callout via a "boost" smart
  422. // pointer. The include file "pkt4.h" typedefs a pointer to the Pkt4
  423. // object as Pkt4Ptr. Retrieve a pointer to the object.
  424. Pkt4Ptr query4_ptr;
  425. handle.getArgument("query4", query4_ptr);
  426. // Point to the hardware address.
  427. HWAddrPtr hwaddr_ptr = query4_ptr->getHWAddr();
  428. // The hardware address is held in a public member variable. We'll classify
  429. // it as interesting if the sum of all the bytes in it is divisible by 4.
  430. // (This is a contrived example after all!)
  431. long sum = 0;
  432. for (int i = 0; i < hwaddr_ptr->hwaddr_.size(); ++i) {
  433. sum += hwaddr_ptr->hwaddr_[i];
  434. }
  435. // Classify it.
  436. if (sum % 4 == 0) {
  437. // Store the text form of the hardware address in the context to pass
  438. // to the next callout.
  439. string hwaddr = hwaddr_ptr->toText();
  440. handle.setContext("hwaddr", hwaddr);
  441. }
  442. return (0);
  443. };
  444. }
  445. @endcode
  446. The "pkt4_receive" callout placed the hardware address of an interesting client in
  447. the "hwaddr" context for the packet. Turning now to the callout that will
  448. write this information to the log file:
  449. @code
  450. // pkt4_send.cc
  451. #include <hooks/hooks.h>
  452. #include <dhcp/pkt4.h>
  453. #include "library_common.h"
  454. #include <string>
  455. using namespace isc::dhcp;
  456. using namespace isc::hooks;
  457. using namespace std;
  458. extern "C" {
  459. // This callout is called at the "pkt4_send" hook.
  460. int pkt4_send(CalloutHandle& handle) {
  461. // Obtain the hardware address of the "interesting" client. We have to
  462. // use a try...catch block here because if the client was not interesting,
  463. // no information would be set and getArgument would thrown an exception.
  464. string hwaddr;
  465. try {
  466. handle.getContext("hwaddr", hwaddr);
  467. // getContext didn't throw so the client is interesting. Get a pointer
  468. // to the reply.
  469. Pkt4Ptr response4_ptr;
  470. handle.getArgument("response4", response4_ptr);
  471. // Get the string form of the IP address.
  472. string ipaddr = response4_ptr->getYiaddr().toText();
  473. // Write the information to the log file.
  474. interesting << hwaddr << " " << ipaddr << "\n";
  475. // ... and to guard against a crash, we'll flush the output stream.
  476. flush(interesting);
  477. } catch (const NoSuchCalloutContext&) {
  478. // No such element in the per-request context with the name "hwaddr".
  479. // This means that the request was not an interesting, so do nothing
  480. // and dismiss the exception.
  481. }
  482. return (0);
  483. }
  484. }
  485. @endcode
  486. @subsection hooksdgLogging Logging in the Hooks Library
  487. Hooks libraries take part in the DHCP message processing. They also often
  488. modify the server's behavior by taking responsibility for processing
  489. the DHCP message at certain stages and instructing the server to skip
  490. the default processing for that stage. Thus, hooks libraries play an
  491. important role in the DHCP server operation and, depending on their
  492. purpose, they may have high complexity, which increases likelihood of the
  493. defects in the libraries.
  494. All hooks libraries should use Kea logging system to facilitate diagnostics
  495. of the defects in the libraries and issues with the DHCP server's operation.
  496. Even if the issue doesn't originate in the hooks library itself, the use
  497. of the library may uncover issues in the Kea code that only
  498. manifest themselves in some special circumstances.
  499. Hooks libraries use the Kea logging system in the same way as any other
  500. standard Kea library. A hooks library should have at least one logger
  501. defined, but may have multiple loggers if it is desired
  502. to separate log messages from different functional parts of the library.
  503. Assuming that it has been decided to use logging in the hooks library, the
  504. implementor must select a unique name for the logger. Ideally the name
  505. should have some relationship with the name of the library so that it is
  506. easy to distinguish messages logged from this library. For example,
  507. if the hooks library is used to capture incoming and outgoing DHCP
  508. messages, and the name of the library is "libkea-packet-capture",
  509. a suitable logger name could be "packet-capture".
  510. In order to use a logger within the library, the logger should be declared
  511. in a header file, which must be included in all files using
  512. the logger:
  513. @code
  514. #ifndef PACKET_CAPTURE_LOG_H
  515. #define PACKET_CAPTURE_LOG_H
  516. #include <log/message_initializer.h>
  517. #include <log/macros.h>
  518. #include <user_chk_messages.h>
  519. namespace packet_capture {
  520. extern isc::log::Logger packet_capture_logger;
  521. }
  522. #endif
  523. @endcode
  524. The logger should be defined and initialized in the implementation file,
  525. as illustrated below:
  526. @code
  527. #include <packet_capture_log.h>
  528. namespace packet_capture {
  529. isc::log::Logger packet_capture_logger("packet-capture");
  530. }
  531. @endcode
  532. These files may contain multiple logger declarations and initializations
  533. when the use of more than one logger is desired.
  534. The next step is to add the appropriate message file as described in the
  535. @ref logMessageFiles.
  536. The implementor must make sure that log messages appear in the right
  537. places and that they are logged at the appropriate level. The choice
  538. of the place where the message should appear is not always obvious:
  539. it depends if the particular function being called already logs enough
  540. information and whether adding log message before and/or after the
  541. call to this function would simply duplicate some messages. Sometimes
  542. the choice whether the log message should appear within the function or
  543. outside of it depends on the level of details available for logging. For
  544. example, in many cases it is desirable to include the client identifier
  545. or transaction id of the DHCP packet being processed in logging message.
  546. If this information is available at the higher level but not in the
  547. function being called, it is often better to place the log message at
  548. higher level. However, the function parameters list could be extended
  549. to include the additional information, and to be logged and the logging
  550. call made from within the function.
  551. Ideally, the hooks library should contain debug log messages (traces)
  552. in all significant decision points in the code, with the information as to
  553. how the code hit this decision point, how it will proceed and why.
  554. However, care should be taken when selecting the log level for those
  555. messages, because selecting too high logging level may impact the
  556. performance of the system. For this reason, traces (messages of
  557. the debug severity) should use different debug levels for the
  558. messages of different importance or having different performance
  559. requirements to generate the log message. For example, generation of
  560. a log message, which prints full details of a packet, usually requires
  561. more CPU bandwidth than the generation of the message which only prints
  562. the packet type and length. Thus, the former should be logged at
  563. lower debug level (see @ref logSeverity for details of using
  564. various debug levels using "dbglevel" parameter).
  565. All loggers defined within the hooks libraries derive the default
  566. configuration from the root logger. For example, when the hooks
  567. library is attached to the DHCPv4 server, the root logger name is
  568. "kea-dhcp4", and the library by default uses configuration of this
  569. logger. The configuration of the library's logger can
  570. be modified by adding a configuration entry for it
  571. to the configuration file. In case of the "packet-capture"
  572. logger declared above, the full name of the logger in the
  573. configuration file will be "kea-dhcp4.packet-capture". The
  574. configuration specified for this logger will override the default
  575. configuration derived from the root logger.
  576. @subsection hooksdgBuild Building the Library
  577. Building the code requires building a sharable library. This requires
  578. the the code be compiled as position-independent code (using the
  579. compiler's "-fpic" switch) and linked as a shared library (with the
  580. linker's "-shared" switch). The build command also needs to point to
  581. the Kea include directory and link in the appropriate libraries.
  582. Assuming that Kea has been installed in the default location, the
  583. command line needed to create the library using the Gnu C++ compiler on a
  584. Linux system is:
  585. @code
  586. g++ -I <install-dir>/include/kea -L <install-dir>/lib -fpic -shared -o example.so \
  587. load_unload.cc pkt4_receive.cc pkt4_send.cc version.cc \
  588. -lkea-dhcpsrv -lkea-dhcp++ -lkea-hooks -lkea-log -lkea-util -lkea-exceptions
  589. @endcode
  590. Notes:
  591. - Replace "<install-dir>" with the location in which you installed Kea. Unless
  592. you specified the "--prefix" switch on the "configure" command line when
  593. building Kea, it will be installed in the default location, usually /usr/local.
  594. - The compilation command and switches required may vary depending on
  595. your operating system and compiler - consult the relevant documentation
  596. for details.
  597. - The list of libraries that need to be included in the command line
  598. depends on the functionality used by the hook code and the module to
  599. which they are attached. Depending on operating system, you may also need
  600. to explicitly list libraries on which the Kea libraries you link against depend.
  601. @subsection hooksdgConfiguration Configuring the Hooks Library
  602. The final step is to make the library known to Kea. The configuration
  603. keywords of all Kea modules to which hooks can be added contain the
  604. "hooks-libraries" element and user libraries are added to this. (The Kea
  605. hooks system can handle multiple libraries - this is discussed below.)
  606. To add the example library (assumed to be in /usr/local/lib) to the
  607. DHCPv4 module, it must be listed in the "hooks-libraries" element of the
  608. "Dhcp4" part of the configuration file:
  609. @code
  610. "Dhcp4": {
  611. :
  612. "hooks-libraries": [
  613. {
  614. "library": "/usr/local/lib/example.so"
  615. }
  616. ]
  617. :
  618. }
  619. @endcode
  620. (Note that "hooks" is plural.)
  621. Each entry in the "hooks-libraries" list is a structure (a "map" in JSON
  622. parlance) that holds the following element:
  623. - library - the name of the library to load. This must be a string.
  624. @note The syntax of the hooks-libraries configuration element has changed
  625. since kea 0.9.2 (in that version, "hooks-libraries" was just a list of
  626. libraries). This change is in preparation for the introduction of
  627. library-specific parameters, which will be added to Kea in a version after 1.0.
  628. The DHCPv4 server will load the library and execute the callouts each time a
  629. request is received.
  630. @note All the above assumes that the hooks library will be used with a
  631. version of Kea that is dynamically-linked. For information regarding
  632. running hooks libraries against a statically-linked Kea, see @ref
  633. hooksdgStaticallyLinkedKea.
  634. @section hooksdgAdvancedTopics Advanced Topics
  635. @subsection hooksdgContextCreateDestroy Context Creation and Destruction
  636. As well as the hooks defined by the server, the hooks framework defines
  637. two hooks of its own, "context_create" and "context_destroy". The first
  638. is called when a request is created in the server, before any of the
  639. server-specific hooks gets called. It's purpose it to allow a library
  640. to initialize per-request context. The second is called after all
  641. server-defined hooks have been processed, and is to allow a library to
  642. tidy up.
  643. As an example, the "pkt4_send" example above required that the code
  644. check for an exception being thrown when accessing the "hwaddr" context
  645. item in case it was not set. An alternative strategy would have been to
  646. provide a callout for the "context_create" hook and set the context item
  647. "hwaddr" to an empty string. Instead of needing to handle an exception,
  648. "pkt4_send" would be guaranteed to get something when looking for
  649. the hwaddr item and so could write or not write the output depending on
  650. the value.
  651. In most cases, "context_destroy" is not needed as the Hooks system
  652. automatically deletes context. An example where it could be required
  653. is where memory has been allocated by a callout during the processing
  654. of a request and a raw pointer to it stored in the context object. On
  655. destruction of the context, that memory will not be automatically
  656. released. Freeing in the memory in the "context_destroy" callout will solve
  657. that problem.
  658. Actually, when the context is destroyed, the destructor
  659. associated with any objects stored in it are run. Rather than point to
  660. allocated memory with a raw pointer, a better idea would be to point to
  661. it with a boost "smart" pointer and store that pointer in the context.
  662. When the context is destroyed, the smart pointer's destructor is run,
  663. which will automatically delete the pointed-to object.
  664. These approaches are illustrated in the following examples.
  665. Here it is assumed that the hooks library is performing some form of
  666. security checking on the packet and needs to maintain information in
  667. a user-specified "SecurityInformation" object. (The details of this
  668. fictitious object are of no concern here.) The object is created in
  669. the "context_create" callout and used in both the "pkt4_receive" and the
  670. "pkt4_send" callouts.
  671. @code
  672. // Storing information in a "raw" pointer. Assume that the
  673. #include <hooks/hooks.h>
  674. :
  675. extern "C" {
  676. // context_create callout - called when the request is created.
  677. int context_create(CalloutHandle& handle) {
  678. // Create the security information and store it in the context
  679. // for this packet.
  680. SecurityInformation* si = new SecurityInformation();
  681. handle.setContext("security_information", si);
  682. }
  683. // Callouts that use the context
  684. int pkt4_receive(CalloutHandle& handle) {
  685. // Retrieve the pointer to the SecurityInformation object
  686. SecurityInformation* si;
  687. handle.getContext("security_information", si);
  688. :
  689. :
  690. // Set the security information
  691. si->setSomething(...);
  692. // The pointed-to information has been updated but the pointer has not been
  693. // altered, so there is no need to call setContext() again.
  694. }
  695. int pkt4_send(CalloutHandle& handle) {
  696. // Retrieve the pointer to the SecurityInformation object
  697. SecurityInformation* si;
  698. handle.getContext("security_information", si);
  699. :
  700. :
  701. // Retrieve security information
  702. bool active = si->getSomething(...);
  703. :
  704. }
  705. // Context destruction. We need to delete the pointed-to SecurityInformation
  706. // object because we will lose the pointer to it when the @c CalloutHandle is
  707. // destroyed.
  708. int context_destroy(CalloutHandle& handle) {
  709. // Retrieve the pointer to the SecurityInformation object
  710. SecurityInformation* si;
  711. handle.getContext("security_information", si);
  712. // Delete the pointed-to memory.
  713. delete si;
  714. }
  715. @endcode
  716. The requirement for the "context_destroy" callout can be eliminated if
  717. a Boost shared ptr is used to point to the allocated memory:
  718. @code
  719. // Storing information in a "raw" pointer. Assume that the
  720. #include <hooks/hooks.h>
  721. #include <boost/shared_ptr.hpp>
  722. :
  723. extern "C" {
  724. // context_create callout - called when the request is created.
  725. int context_create(CalloutHandle& handle) {
  726. // Create the security information and store it in the context for this
  727. // packet.
  728. boost::shared_ptr<SecurityInformation> si(new SecurityInformation());
  729. handle.setContext("security_information", si);
  730. }
  731. // Other than the data type, a shared pointer has similar semantics to a "raw"
  732. // pointer. Only the code from "pkt4_receive" is shown here.
  733. int pkt4_receive(CalloutHandle& handle) {
  734. // Retrieve the pointer to the SecurityInformation object
  735. boost::shared_ptr<SecurityInformation> si;
  736. handle.setContext("security_information", si);
  737. :
  738. :
  739. // Modify the security information
  740. si->setSomething(...);
  741. // The pointed-to information has been updated but the pointer has not
  742. // altered, so there is no need to reset the context.
  743. }
  744. // No context_destroy callout is needed to delete the allocated
  745. // SecurityInformation object. When the @c CalloutHandle is destroyed, the shared
  746. // pointer object will be destroyed. If that is the last shared pointer to the
  747. // allocated memory, then it too will be deleted.
  748. @endcode
  749. (Note that a Boost shared pointer - rather than any other Boost smart pointer -
  750. should be used, as the pointer objects are copied within the hooks framework and
  751. only shared pointers have the correct behavior for the copy operation.)
  752. @subsection hooksdgCalloutRegistration Registering Callouts
  753. As briefly mentioned in @ref hooksdgExampleCallouts, the standard is for
  754. callouts in the user library to have the same name as the name of the
  755. hook to which they are being attached. This convention was followed
  756. in the tutorial, e.g., the callout that needed to be attached to the
  757. "pkt4_receive" hook was named pkt4_receive.
  758. The reason for this convention is that when the library is loaded, the
  759. hook framework automatically searches the library for functions with
  760. the same names as the server hooks. When it finds one, it attaches it
  761. to the appropriate hook point. This simplifies the loading process and
  762. bookkeeping required to create a library of callouts.
  763. However, the hooks system is flexible in this area: callouts can have
  764. non-standard names, and multiple callouts can be registered on a hook.
  765. @subsubsection hooksdgLibraryHandle The LibraryHandle Object
  766. The way into the part of the hooks framework that allows callout
  767. registration is through the LibraryHandle object. This was briefly
  768. introduced in the discussion of the framework functions, in that
  769. an object of this type is pass to the "load" function. A LibraryHandle
  770. can also be obtained from within a callout by calling the CalloutHandle's
  771. @c getLibraryHandle() method.
  772. The LibraryHandle provides three methods to manipulate callouts:
  773. - @c registerCallout - register a callout on a hook.
  774. - @c deregisterCallout - deregister a callout from a hook.
  775. - @c deregisterAllCallouts - deregister all callouts on a hook.
  776. The following sections cover some of the ways in which these can be used.
  777. @subsubsection hooksdgNonstandardCalloutNames Non-Standard Callout Names
  778. The example in the tutorial used standard names for the callouts. As noted
  779. above, it is possible to use non-standard names. Suppose, instead of the
  780. callout names "pkt4_receive" and "pkt4_send", we had named our callouts
  781. "classify" and "write_data". The hooks framework would not have registered
  782. these callouts, so we would have needed to do it ourself. The place to
  783. do this is the "load" framework function, and its code would have had to
  784. been modified to:
  785. @code
  786. int load(LibraryHandle& libhandle) {
  787. // Register the callouts on the hooks. We assume that a header file
  788. // declares the "classify" and "write_data" functions.
  789. libhandle.registerCallout("pkt4_receive", classify);
  790. libhandle.registerCallout("pkt4_send", write_data);
  791. // Open the log file
  792. interesting.open("/data/clients/interesting.log",
  793. std::fstream::out | std::fstream::app);
  794. return (interesting ? 0 : 1);
  795. }
  796. @endcode
  797. It is possible for a library to contain callouts with both standard and
  798. non-standard names: ones with standard names will be registered automatically,
  799. ones with non-standard names need to be registered manually.
  800. @subsubsection hooksdgMultipleCallouts Multiple Callouts on a Hook
  801. The Kea hooks framework allows multiple callouts to be attached to
  802. a hook point. Although it is likely to be rare for user code to need to
  803. do this, there may be instances where it make sense.
  804. To register multiple callouts on a hook, just call
  805. @c LibraryHandle::registerCallout multiple times on the same hook, e.g.,
  806. @code
  807. libhandle.registerCallout("pkt4_receive", classify);
  808. libhandle.registerCallout("pkt4_receive", write_data);
  809. @endcode
  810. The hooks framework will call the callouts in the order they are
  811. registered. The same @c CalloutHandle is passed between them, so any
  812. change made to the CalloutHandle's arguments, "skip" flag, or per-request
  813. context by the first is visible to the second.
  814. @subsubsection hooksdgDynamicRegistration Dynamic Registration and Re-registration of Callouts
  815. The previous sections have dealt with callouts being registered during
  816. the call to "load". The hooks framework is more flexible than that
  817. in that callouts can be registered and deregistered within a callout.
  818. In fact, a callout is able to register or deregister itself, and a callout
  819. is able to be registered on a hook multiple times.
  820. Using our contrived example again, the DHCPv4 server processes one request
  821. to completion before it starts processing the next. With this knowledge,
  822. we could alter the logic of the code so that the callout attached to the
  823. "pkt4_receive" hook registers the callout doing the logging when it detects
  824. an interesting packet, and the callout doing the logging deregisters
  825. itself in its execution. The relevant modifications to the code in
  826. the tutorial are shown below:
  827. @code
  828. // pkt4_receive.cc
  829. // :
  830. int pkt4_receive(CalloutHandle& handle) {
  831. :
  832. :
  833. // Classify it.
  834. if (sum % 4 == 0) {
  835. // Store the text form of the hardware address in the context to pass
  836. // to the next callout.
  837. handle.setContext("hwaddr", hwaddr_ptr->hwaddr_.toText());
  838. // Register the callback to log the data.
  839. handle.getLibraryHandle().registerCallout("pkt4_send", write_data);
  840. }
  841. return (0);
  842. };
  843. @endcode
  844. @code
  845. // pkt4_send.cc
  846. :
  847. int write_data(CalloutHandle& handle) {
  848. // Obtain the hardware address of the "interesting" client. As the
  849. // callback is only registered when interesting data is present, we
  850. // know that the context contains the hardware address so an exception
  851. // will not be thrown when we call getArgument().
  852. string hwaddr;
  853. handle.getArgument("hwaddr", hwaddr);
  854. // The pointer to the reply.
  855. ConstPkt4Ptr reply;
  856. handle.getArgument("reply", reply);
  857. // Get the string form of the IP address.
  858. string ipaddr = reply->getYiaddr().toText():
  859. // Write the information to the log file and flush.
  860. interesting << hwaddr << " " << ipaddr << "\n";
  861. flush(interesting);
  862. // We've logged the data, so deregister ourself. This callout will not
  863. // be called again until it is registered by "pkt4_receive".
  864. handle.getLibraryHandle().deregisterCallout("pkt4_send", write_data);
  865. return (0);
  866. }
  867. @endcode
  868. Note that the above example used a non-standard name for the callout
  869. that wrote the data. Had the name been a standard one, it would have been
  870. registered when the library was loaded and called for the first request,
  871. regardless of whether that was defined as "interesting". (Although as
  872. callouts with standard names are always registered before "load" gets called,
  873. we could have got round that problem by deregistering that particular
  874. callout in the "load" function.)
  875. @note Deregistration of a callout on the hook that is currently
  876. being called only takes effect when the server next calls the hook.
  877. To illustrate this, suppose the callouts attached to a hook are A, B and C
  878. (in that order), and during execution, A deregisters B and C and adds D.
  879. When callout A returns, B and C will still run. The next time the server
  880. calls the hook's callouts, A and D will run (in that order).
  881. @subsection hooksdgMultipleLibraries Multiple User Libraries
  882. As alluded to in the section @ref hooksdgConfiguration, Kea can load
  883. multiple libraries. The libraries are loaded in the order specified in
  884. the configuration, and the callouts attached to the hooks in the order
  885. presented by the libraries.
  886. The following picture illustrates this, and also illustrates the scope of
  887. data passed around the system.
  888. @image html DataScopeArgument.png "Scope of Arguments"
  889. In this illustration, a server has three hook points, alpha, beta
  890. and gamma. Two libraries are configured, library 1 and library 2.
  891. Library 1 registers the callout "authorize" for hook alpha, "check" for
  892. hook beta and "add_option" for hook gamma. Library 2 registers "logpkt",
  893. "validate" and "putopt"
  894. The horizontal red lines represent arguments to callouts. When the server
  895. calls hook alpha, it creates an argument list and calls the
  896. first callout for the hook, "authorize". When that callout returns, the
  897. same (but possibly modified) argument list is passed to the next callout
  898. in the chain, "logpkt". Another, separate argument list is created for
  899. hook beta and passed to the callouts "check" and "validate" in
  900. that order. A similar sequence occurs for hook gamma.
  901. The next picture shows the scope of the context associated with a
  902. request.
  903. @image html DataScopeContext.png "Illustration of per-library context"
  904. The vertical blue lines represent callout context. Context is
  905. per-packet but also per-library. When the server calls "authorize",
  906. the CalloutHandle's @c getContext and @c setContext methods access a context
  907. created purely for library 1. The next callout on the hook will access
  908. context created for library 2. These contexts are passed to the callouts
  909. associated with the next hook. So when "check" is called, it gets the
  910. context data that was set by "authorize", when "validate" is called,
  911. it gets the context data set by "logpkt".
  912. It is stressed that the context for callouts associated with different
  913. libraries is entirely separate. For example, suppose "authorize" sets
  914. the CalloutHandle's context item "foo" to 2 and "logpkt" sets an item of
  915. the same name to the string "bar". When "check" accesses the context
  916. item "foo", it gets a value of 2; when "validate" accesses an item of
  917. the same name, it gets the value "bar".
  918. It is also stressed that all this context exists only for the life of the
  919. request being processed. When that request is complete, all the
  920. context associated with that request - for all libraries - is destroyed,
  921. and new context created for the next request.
  922. This structure means that library authors can use per-request context
  923. without worrying about the presence of other libraries. Other libraries
  924. may be present, but will not affect the context values set by a library's
  925. callouts.
  926. Configuring multiple libraries just requires listing the libraries
  927. as separate elements of the hooks-libraries configuration element, e.g.,
  928. @code
  929. "Dhcp4": {
  930. :
  931. "hooks-libraries": [
  932. {
  933. "library": "/usr/lib/library1.so"
  934. },
  935. {
  936. "library": "/opt/library2.so"
  937. }
  938. :
  939. ]
  940. }
  941. @endcode
  942. @subsection hooksdgInterLibraryData Passing Data Between Libraries
  943. In rare cases, it is possible that one library may want to pass
  944. data to another. This can be done in a limited way by means of the
  945. CalloutHandle's @c setArgument and @c getArgument calls. For example, in the
  946. above diagram, the callout "add_option" can pass a value to "putopt"
  947. by setting a name.value pair in the hook's argument list. "putopt"
  948. would be able to read this, but would not be able to return information
  949. back to "add_option".
  950. All argument names used by Kea will be a combination of letters
  951. (both upper- and lower-case), digits, hyphens and underscores: no
  952. other characters will be used. As argument names are simple strings,
  953. it is suggested that if such a mechanism be used, the names of the data
  954. values passed between the libraries include a special character such as
  955. the dollar symbol or percent sign. In this way there is no danger that
  956. a name will conflict with any existing or future Kea argument names.
  957. @subsection hooksdgRegisterMultipleLibraries Dynamic Callout Registration and Multiple Libraries
  958. On a particular hook, callouts are called in the order the libraries appear
  959. in the configuration and, within a library, in the order the callouts
  960. are registered.
  961. This order applies to dynamically-registered callouts as well. As an
  962. example, consider the diagram above where for hook "beta", callout "check"
  963. is followed by callout "validate". Suppose that when "authorize" is run,
  964. it registers a new callout ("double_check") on hook "beta". That
  965. callout will be inserted at the end of the callouts registered by
  966. library 1 and before any registered by library 2. It would therefore
  967. appear between "check" and "validate". On the other hand, if it were
  968. "logpkt" that registered the new callout, "double_check" would appear
  969. after "validate".
  970. @subsection hooksdgStaticallyLinkedKea Running Against a Statically-Linked Kea
  971. If Kea is built with the --enable-static-link switch (set when
  972. running the "configure" script), no shared Kea libraries are built;
  973. instead, archive libraries are created and Kea is linked to them.
  974. If you create a hooks library also linked against these archive libraries,
  975. when the library is loaded you end up with two copies of the library code,
  976. one in Kea and one in your library.
  977. To run successfully, your library needs to perform run-time initialization
  978. of the Kea code in your library (something performed by Kea
  979. in the case of shared libraries). To do this, call the function
  980. isc::hooks::hooksStaticLinkInit() as the first statement of the load()
  981. function. (If your library does not include a load() function, you need
  982. to add one.) For example:
  983. @code
  984. #include <hooks/hooks.h>
  985. extern "C" {
  986. int version() {
  987. return (KEA_HOOKS_VERSION);
  988. }
  989. int load() {
  990. isc::hooks::hooksStaticLinkInit();
  991. :
  992. }
  993. // Other callout functions
  994. :
  995. }
  996. @endcode
  997. @subsection hooksdgHooksConfig Configuring Hooks Libraries
  998. Sometimes it is useful for the hook library to have some configuration parameters.
  999. This capability was introduced in Kea 1.1. This is often convenient to follow
  1000. generic Kea configuration approach rather than invent your own configuration
  1001. logic. Consider the following example:
  1002. @code
  1003. "hooks-libraries": [
  1004. {
  1005. "library": "/opt/first.so"
  1006. },
  1007. {
  1008. "library": "/opt/second.so",
  1009. "parameters": {
  1010. }
  1011. },
  1012. {
  1013. "library": "/opt/third.so",
  1014. "parameters": {
  1015. "mail": "spam@example.com",
  1016. "floor": 13,
  1017. "debug": false,
  1018. "users": [ "alice", "bob", "charlie" ],
  1019. "languages": {
  1020. "french": "bonjour",
  1021. "klingon": "yl'el"
  1022. }
  1023. }
  1024. }
  1025. ]
  1026. @endcode
  1027. This example has three hook libraries configured. The first and and second have
  1028. no parameters. Note that parameters map is optional, but it's perfectly ok to
  1029. specify it as an empty map. The third library is more interesting. It has five
  1030. parameters specified. The first one called 'mail' is a string. The second one
  1031. is an integer and the third one is boolean. Fourth and fifth parameters are
  1032. slightly more complicated as they are a list and a map respectively. JSON
  1033. structures can be nested if necessary, e.g., you can have a list, which contains
  1034. maps, maps that contain maps that contain other maps etc. Any valid JSON
  1035. structure can be represented. One important limitation here is that the top
  1036. level "parameters" structure must either be a map or not present at all.
  1037. Those parameters can be accessed in load() method. Passed isc::hooks::LibraryHandle
  1038. object has a method called getParameter that returns an instance of
  1039. isc::data::ConstElementPtr or null pointer if there was no parameter specified. This pointer
  1040. will point to an object derived from isc::data::Element class. For detailed
  1041. explanation how to use those objects, see isc::data::Element class.
  1042. Here's a brief overview of how to use those elements:
  1043. - x = getParameter("mail") will return instance of isc::data::StringElement. The content
  1044. can be accessed with x->stringValue() and will return std::string.
  1045. - x = getParameter("floor") will return an instance of isc::data::IntElement.
  1046. The content can be accessed with x->intValue() and will return int.
  1047. - x = getParameter("debug") will return an instance of isc::data::BoolElement.
  1048. Its value can be accessed with x->boolValue() and will return bool.
  1049. - x = getParameter("users") will return an instance of isc::data::ListElement.
  1050. Its content can be accessed with the following methods:
  1051. x->size(), x->get(index)
  1052. - x = getParameter("watch-list") will return an instance of isc::data::MapElement.
  1053. Its content can be accessed with the following methods:
  1054. x->find("klingon"), x->contains("french"), x->size()
  1055. Keep in mind that the user can structure his config file incorrectly.
  1056. Remember to check if the structure has the expected type before using type specific
  1057. method. For example calling stringValue on IntElement will throw an exception.
  1058. You can do this by calling isc::data::Element::getType.
  1059. Here's an example that obtains all of the parameters specified above.
  1060. If you want to get nested elements, Element::get(index) and Element::find(name)
  1061. will return ElementPtr, which can be iterated in similar manner.
  1062. @code
  1063. int load(LibraryHandle& handle) {
  1064. ConstElementPtr mail = handle.getParameter("mail");
  1065. ConstElementPtr floor = handle.getParameter("floor");
  1066. ConstElementPtr debug = handle.getParameter("debug");
  1067. ConstElementPtr users = handle.getParameter("users");
  1068. ConstElementPtr lang = handle.getParameter("languages");
  1069. // String handling example
  1070. if (!mail) {
  1071. // Handle missing 'mail' parameter here.
  1072. return (1);
  1073. }
  1074. if (mail->getType() != Element::string) {
  1075. // Handle incorrect 'mail' parameter here.
  1076. return (1);
  1077. }
  1078. std::string mail_str = mail->stringValue();
  1079. // In the following examples safety checks are omitted for clarity.
  1080. // Make sure you do it properly similar to mail example above
  1081. // or you risk dereferencing null pointer or at least throwing
  1082. // an exception!
  1083. // Integer handling example
  1084. int floor_num = floor->intValue();
  1085. // Boolean handling example
  1086. bool debug_flag = debug->boolValue();
  1087. // List handling example
  1088. std::cout << "There are " << users->size() << " users defined." << std::endl;
  1089. for (int i = 0; i < users->size(); i++) {
  1090. ConstElementPtr user = users->get(i);
  1091. std::cout << "User " << user->stringValue() << std::endl;
  1092. }
  1093. // Map handling example
  1094. std::cout << "There are " << lang->size() << " languages defined." << std::endl;
  1095. if (lang->contains("french")) {
  1096. std::cout << "One of them is French!" << std::endl;
  1097. }
  1098. ConstElementPtr greeting = lang->find("klingon");
  1099. if (greeting) {
  1100. std::cout << "Lt. Worf says " << greeting->stringValue() << std::endl;
  1101. }
  1102. // All validation steps were successful. The library has all the parameters
  1103. // it needs, so we should report a success.
  1104. return (0);
  1105. }
  1106. @endcode
  1107. A good sources of examples could be unit-tests in file src/lib/cc/tests/data_unittests.cc
  1108. which are dedicated to isc::data::Element testing and src/lib/hooks/tests/callout_params_library.cc,
  1109. which is an example library used in testing. This library expects exactly 3 parameters:
  1110. svalue (which is a string), ivalue (which is an integer) and bvalue (which is a boolean).
  1111. */