.\" Automatically generated by Pod::Man 4.10 (Pod::Simple 3.35) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. . \" fudge factors for nroff and troff .if n \{\ . ds #H 0 . ds #V .8m . ds #F .3m . ds #[ \f1 . ds #] \fP .\} .if t \{\ . ds #H ((1u-(\\\\n(.fu%2u))*.13m) . ds #V .6m . ds #F 0 . ds #[ \& . ds #] \& .\} . \" simple accents for nroff and troff .if n \{\ . ds ' \& . ds ` \& . ds ^ \& . ds , \& . ds ~ ~ . ds / .\} .if t \{\ . ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" . ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' . ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' . ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' . ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' . ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' .\} . \" troff and (daisy-wheel) nroff accents .ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' .ds 8 \h'\*(#H'\(*b\h'-\*(#H' .ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] .ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' .ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' .ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] .ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] .ds ae a\h'-(\w'a'u*4/10)'e .ds Ae A\h'-(\w'A'u*4/10)'E . \" corrections for vroff .if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' .if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' . \" for low resolution devices (crt and lpr) .if \n(.H>23 .if \n(.V>19 \ \{\ . ds : e . ds 8 ss . ds o a . ds d- d\h'-1'\(ga . ds D- D\h'-1'\(hy . ds th \o'bp' . ds Th \o'LP' . ds ae ae . ds Ae AE .\} .rm #[ #] #H #V #F C .\" ======================================================================== .\" .IX Title "docs::gdnsd-plugin-api 3" .TH docs::gdnsd-plugin-api 3 "2019-03-05" "gdnsd 2.4.2" "gdnsd" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" gdnsd\-plugin\-api \- How to write gdnsd plugin code .SH "SYNOPSIS" .IX Header "SYNOPSIS" .Vb 3 \& Mandatory preamble macro+header your source must include at the top: \& #define GDNSD_PLUGIN_NAME foo \& #include \& \& Callback hooks you may implement (all are optional, and executed in this order): \& (Letters in brackets denote callbacks applicable to: R for Resolver plugin role \& and/or M for Monitor plugin role; a plugin may implement one or both). \& \-\- startup/config stuff: \& # only \*(Aqcheckconf\*(Aq, \*(Aqstart\*(Aq, \*(Aqrestart\*(Aq, \*(Aqcondrestart\*(Aq invoke plugin callbacks at all \& [RM] void plugin_foo_load_config(vscf_data_t* pc, const unsigned num_threads) \& [ M] void plugin_foo_add_svctype(const char* name, vscf_data_t* svc_cfg, const unsigned interval, const unsigned timeout) \& [ M] void plugin_foo_add_mon_addr(const char* desc, const char* svc_name, const char* cname, const dmn_anysin_t* addr, const unsigned idx); \& [ M] void plugin_foo_add_mon_cname(const char* desc, const char* svc_name, const char* cname, const unsigned idx); \& # only \*(Aqstart\*(Aq, \*(Aqrestart\*(Aq, and \*(Aqcondrestart\*(Aq continue past this point \& [ M] void plugin_foo_init_monitors(struct ev_loop* mon_loop) \& [ M] void plugin_foo_start_monitors(struct ev_loop* mon_loop) \& [R ] void plugin_foo_pre_run() \& [R ] void plugin_foo_iothread_init(unsigned threadnum) \& \& \-\- runtime stuff (called from main or zonefile thread) \& \-\- (you won\*(Aqt get parallel calls to this, and in general it should be a readonly \& \-\- operation anyways) \& [R ] int plugin_foo_map_res(const char* resname, const uint8_t* origin) \& \& \-\- runtime stuff (called from iothread context, anytime after iothread_init()) \& [R ] gdnsd_sttl_t plugin_foo_resolve(unsigned resnum, const uint8_t* origin, const client_info_t* cinfo, dyn_result_t* result) \& \& \-\- cleanup stuff: \& [RM] void plugin_foo_exit(void) .Ve .SH "WARNING" .IX Header "WARNING" Please note that in general, gdnsd's plugin \s-1API\s0 is poorly documented and unstable. It often goes through fairly large and abrupt changes during development cycles, although it tends to be stable for a given stable release series. Write code against it at your own peril (or at least, let me know so I can give you some warning on upcoming changes and/or solicit your feedback!). .SH "OVERVIEW" .IX Header "OVERVIEW" This file documents versions 15\-16 of the gdnsd plugin \s-1API.\s0 .PP gdnsd's plugin \s-1API\s0 offers the ability to write plugins that can do either (or both) of two roles: .PP 1) Dynamically generate virtual \f(CW\*(C`A\*(C'\fR, \f(CW\*(C`AAAA\*(C'\fR, and/or \f(CW\*(C`CNAME\*(C'\fR records according to whatever logic the plugin author wishes. The plugin can make use of gdnsd's monitoring services for being failover-aware, and the actual zonefile records that trigger these lookups are \f(CW\*(C`DYNA\*(C'\fR (for address-only data) and \f(CW\*(C`DYNC\*(C'\fR (for which the plugin can return \&\f(CW\*(C`CNAME\*(C'\fR or address results). .PP 2) Provide custom protocols and implementations for the back-end of the monitoring code for use by any plugin. In this case you mostly just implement the protocol check code against a standard libev event loop and use a helper function to report the results of each status check, and the core takes care of the rest. .PP All callbacks can be implemented by all plugins; it is possible to create a combined plugin that performs both roles. There is no clear distinction between plugin \*(L"types\*(R" internally. .SH "USER-LEVEL CONFIGURATION FOR RESOLVER PLUGINS" .IX Header "USER-LEVEL CONFIGURATION FOR RESOLVER PLUGINS" If you haven't read the documentation for the overall configuration file (gdnsd.config) and the zonefiles (gdnsd.zonefile), you might want to read those before continuing. .PP From a user's perspective, there are two parts to configuring plugins. The first is configuring the plugin via the gdnsd config file. The config file has an optional \f(CW\*(C`plugins\*(C'\fR hash. The keys of this hash are the names of plugins to load, and the values (which must be hashes) are the configuration data for the plugin itself. e.g., to load two plugins named \f(CW\*(C`foo\*(C'\fR and \f(CW\*(C`bar\*(C'\fR, the plugins hash might look like this: .PP .Vb 10 \& plugins => { \& foo => { \& opts => { \& something = "quux\e000<\-an_embedded_null!", \& somethingelse = { Z => z }, \& }, \& xyz = [x, y, z] \& } \& bar => { x => y } \& } .Ve .PP Note that a short-form plugin name (e.g. \f(CW\*(C`foo\*(C'\fR) maps to a shared library named \fIplugin_foo.so\fR. Plugins will be loaded from the directory \fI/usr/lib/x86_64\-linux\-gnu/gdnsd\fR by default, but this path can be overridden in the \f(CW\*(C`options\*(C'\fR section of the gdnsd configuration. .PP The basic syntactic structure of your plugin's config hash follows the same rules as the gdnsd config as a whole. This is the \f(CW\*(C`vscf\*(C'\fR syntax, which allows the user to specify nested data in the form of hashes, arrays, and simple values. It's entirely up to the plugin author how the contents of the hash should be interpreted, and to document the plugin's config hash for users. .PP The second part of the configuration is inserting \f(CW\*(C`DYNA\*(C'\fR and/or \&\f(CW\*(C`DYNC\*(C'\fR resource records into zonefiles. \f(CW\*(C`DYNA\*(C'\fR RRs use a plugin to dynamically generate \f(CW\*(C`A\*(C'\fR and/or \f(CW\*(C`AAAA\*(C'\fR RRs, while \f(CW\*(C`DYNC\*(C'\fR RRs use a plugin to dynamically generate either \f(CW\*(C`A\*(C'\fR/\f(CW\*(C`AAAA\*(C'\fR RRs or \f(CW\*(C`CNAME\*(C'\fR RRs. .PP .Vb 3 \& www 300 DYNA foo!prod_web \& www.test 300 DYNA foo!test_web \& web 300 DYNC bar!test_web_cname .Ve .PP The initial parts (the left-hand domainname, \s-1TTL,\s0 and RR-type) follow the usual zonefile norms, other than the fact that \f(CW\*(C`DYNA\*(C'\fR is not a real resource record type in the \s-1DNS\s0 protocol. The rdata section (e.g. \&\f(CW\*(C`foo!prod_web\*(C'\fR) contains two parts separated by an \f(CW\*(C`!\*(C'\fR: A plugin name, and a resource name. .PP The meaning of the resource name is entirely up to the plugin. Typically it will reference a configuration key from the plugin's configuration hash as a mapping to a specific set of parameters for the plugin, but other uses of this field are possible. .PP Plugins may implement just address results, just \s-1CNAME\s0 results, or both. .SH "USER-LEVEL CONFIGURATION FOR MONITORING" .IX Header "USER-LEVEL CONFIGURATION FOR MONITORING" \&\s-1DYNA/DYNC\s0 plugin code can optionally take advantage of monitoring services, e.g. to not return \*(L"dead\*(R" addresses from a pool. Monitoring is configured as a set of \f(CW\*(C`service_types\*(C'\fR, each representing a protocol, protocol-specific parameters, and some generic parameters related to timing and anti-flap. e.g.: .PP .Vb 10 \& service_types = { \& prod_web = { \& plugin = http_status \& # plugin\-specific parameters \& vhost = www.example.com \& url_path = /checkme \& ok_codes = [ 200, 201 ] \& # generic parameters \& up_thresh = 24 \& down_thresh = 16 \& ok_thresh = 8 \& interval = 8 \& timeout = 4 \& } \& } .Ve .PP A service type is meant to be re-used to monitor the same service at several different addresses or CNAMEs. .PP One of the service type parameters is \f(CW\*(C`plugin\*(C'\fR, naming a custom monitoring plugin to load. If this plugin was not listed directly in the \f(CW\*(C`plugins\*(C'\fR hash to give it global-level configuration, it will be loaded with no configuration at all (\f(CW\*(C`_load_config(NULL)\*(C'\fR). .SH "PLUGIN SOURCE ORGANIZATION" .IX Header "PLUGIN SOURCE ORGANIZATION" There must be one primary plugin source file which implements the callback hooks, and this file must include the following before any other code: .PP .Vb 2 \& #define GDNSD_PLUGIN_NAME foo \& #include .Ve .PP If you wish to split your implementation over multiple files, you can access the relevant \s-1API\s0 interfaces via the other \f(CW\*(C`gdnsd/*.h\*(C'\fR headers directly. However all of the actual callback hooks must be implemented in the primary source file, and your other source files should \fBnot\fR include \f(CW\*(C`gdnsd/plugin.h\*(C'\fR. .SH "RUNTIME CALLBACK FLOW" .IX Header "RUNTIME CALLBACK FLOW" To understand how plugins operate and how to write plugins, it is necessary to understand the overall flow of gdnsd's execution, and where in that flow various callbacks are made into the code of the loaded plugins. If you haven't yet read the main gdnsd daemon documentation at this point, now would be a good time, as it covers some basic info about how gdnsd acts as its own initscript. All callbacks have the name of the plugin in the function name, and we will use the example name \f(CW\*(C`foo\*(C'\fR for documentation purposes. A brief summary of all of the \s-1API\s0 interfaces and semantics follows in a later section, but it would be good to read through this lengthy prose explanation at least once. .SS "\s-1CONFIGURATION\s0" .IX Subsection "CONFIGURATION" When gdnsd is started via actions such as \f(CW\*(C`start\*(C'\fR, \f(CW\*(C`restart\*(C'\fR or \&\f(CW\*(C`condrestart\*(C'\fR, or when configuration is checked via \f(CW\*(C`checkconf\*(C'\fR, at least some of the plugin callbacks will be executed. .PP As soon as the configuration file as a whole has been validated and loaded, gdnsd goes about setting various internal parameters from this data. When it encounters the \f(CW\*(C`plugins\*(C'\fR hash, it will load and configure the named plugins. Immediately after loading each plugin, it will execute the \f(CW\*(C`plugin_foo_load_config()\*(C'\fR callback, providing the plugin code with its vscf configuration hash. At this time the plugin should walk (and validate) the provided configuration data and set up its own internal parameters based on this data. Any expensive configuration steps should be avoided in the load_config callback. Your goal in load_config is to validate your configuration data and store it somewhere, nothing more. .PP There are 3 special \s-1API\s0 calls that are only valid during the execution of \f(CW\*(C`plugin_foo_load_config()\*(C'\fR and only by resolver plugins, which are used by the plugin to feed some configuration-based data back to the core code. These are \f(CW\*(C`gdnsd_mon_addr()\*(C'\fR and \f(CW\*(C`gdnsd_mon_cname()\*(C'\fR (which are used by resolver plugins to ask the monitoring system to monitor addresses and/or CNAMEs), and \f(CW\*(C`gdnsd_dyn_addr_max()\*(C'\fR, which must be called to inform the core code of the maximum address counts this plugin configuration could ever return in a single response. Failure to call \f(CW\*(C`gdnsd_dyn_addr_max()\*(C'\fR results in the core assuming a maximum of 1 address per family. .PP Next, \f(CW\*(C`service_types\*(C'\fR are processed from the config. These may autoload additional plugins that were not specified in the \f(CW\*(C`plugins\*(C'\fR hash. They will also receive a \f(CW\*(C`plugin_foo_load_config(NULL)\*(C'\fR call if autoloaded. .PP For each service type that uses a given plugin, the plugin will receive a \f(CW\*(C`plugin_foo_add_svctype()\*(C'\fR callback. Use this to set up local data structures for each service type you've been assigned. .PP Next, all of the specific monitoring requested earlier by resolver plugins (via \f(CW\*(C`gdnsd_mon_addr()\*(C'\fR and \f(CW\*(C`gdnsd_mon_cname()\*(C'\fR) is passed to the monitoring plugins by invoking their \f(CW\*(C`plugin_foo_add_mon_addr()\*(C'\fR and \&\f(CW\*(C`plugin_foo_add_mon_cname()\*(C'\fR. This is when a monitoring plugin sets up per\-address/CNAME data structures. .PP After all of the above, the daemon loads and parses all zonefiles, constructing the internal runtime \s-1DNS\s0 database. During the zonefile loading phase, when it encounters \f(CW\*(C`DYNA\*(C'\fR RRs in zonefiles, they will trigger the plugin callback \f(CW\*(C`plugin_foo_map_res\*(C'\fR once for every \f(CW\*(C`DYNA\*(C'\fR \s-1RR,\s0 with a \f(CW\*(C`NULL\*(C'\fR \f(CW\*(C`origin\*(C'\fR argument. The same occurs with all \f(CW\*(C`DYNC\*(C'\fR RRs, and they will get non\-\f(CW\*(C`NULL\*(C'\fR \f(CW\*(C`origin\*(C'\fR arguments, which indicate the current \f(CW$ORIGIN\fR in effect for the \s-1RR.\s0 It is important to note that your plugin should treat it as an error if it gets a \f(CW\*(C`_map_res\*(C'\fR call with a \f(CW\*(C`NULL\*(C'\fR \f(CW\*(C`origin\*(C'\fR (\s-1DYNA\s0) for a resource which is configured to be capable of returning \f(CW\*(C`CNAME\*(C'\fR results. .PP If your \s-1DYNC\s0 plugin supports variable origins (e.g. the same resource name can be re-used in multiple zonefiles, and prepends some standard domainname fragment to origin in effect for the given \s-1RR\s0), it is important that you validate that you can construct a legal domainname (length limits) from the given origin, resource name, and your own config at this time. .PP Plugins should \fBnot\fR return different resource numbers for the same resname argument regardless of \f(CW\*(C`origin\*(C'\fR value (or lack thereof). You will break things if you do so. .PP If your map_resource operation fails (e.g. unknown resource name, or illegal origin-based \f(CW\*(C`CNAME\*(C'\fR construction, or a \s-1NULL\s0 origin argument (\s-1DYNA\s0) for a resource that could return \f(CW\*(C`CNAME\*(C'\fR data), log the error and return \-1. Do \fBnot\fR fail fatally, as these calls happen at runtime during dynamic zonefile reloads. .PP In the case of the action \f(CW\*(C`checkconf\*(C'\fR, execution stops here. Only the \&\f(CW\*(C`start\*(C'\fR and \f(CW\*(C`restart\*(C'\fR actions continue on to become full-fledged daemon instances. .PP The first is \f(CW\*(C`plugin_foo_init_monitors()\*(C'\fR. You will be passed the event loop, and you are expected to set up events that will do a single monitoring check on all monitored resources and then clear themselves and not repeat. When all plugins have done their \fBinit_monitors()\fR, the loop will be run, and it is expected to terminate after a few seconds when all monitoring states have been initialized with real-world data. .PP The next is \f(CW\*(C`plugin_foo_start_monitors()\*(C'\fR. Again you are passed the same libev loop, and you add all of your monitored resource callbacks, but this time it's permanent: they're expected to repeat their monitoring checks endlessly the next time the loop is invoked. .PP When your libev monitoring callbacks have determined a success or failure for a monitored resource, they're expected to call the helper function \f(CW\*(C`gdnsd_mon_state_updater()\*(C'\fR from \fIgdnsd/mon.h\fR to send the state info upstream for anti-flap calculations and re-destribution to plugins which are monitoring the given resource. .PP \&\f(CW\*(C`plugin_foo_pre_run\*(C'\fR is executed next, giving a final chance to run any single-threaded setup code before threads are spawned and we enter runtime operations. .PP After pre_run, gdnsd will spawn the runtime \s-1DNS I/O\s0 threads. For each such thread, the callback \f(CW\*(C`plugin_foo_iothread_init\*(C'\fR will be called from within each I/O thread with the global thread number as the only argument (0 through num_threads\-1, where num_threads was provided to you back at \f(CW\*(C`plugin_foo_load_config()\*(C'\fR time). This would be the ideal time to \fBxmalloc()\fR writable per-thread data structures from within the threads themselves, so that a thread-aware malloc can avoid false sharing. .SS "\s-1RUNTIME\s0" .IX Subsection "RUNTIME" At this point, gdnsd is ready to begin serving \s-1DNS\s0 queries. After all I/O threads have finished initialization (and thus moved on to already serving requests), the primary thread will do its own thing for managing daemon lifecycle and signals and such. .PP During runtime the only direct callbacks your plugin will receive from I/O thread contexts are \f(CW\*(C`plugin_foo_resolve\*(C'\fR and \f(CW\*(C`plugin_foo_map_res\*(C'\fR. .PP As a general style rule, the runtime resolver callback is not allowed to block or fail. It is expected to respond immediately with valid response data. It is your job as the plugin author to ensure this is the case. That means pre-allocating memory, pre-loading data, and/or pre-calculating anything expensive during earlier callbacks. Worst case, you can return meaningless data, e.g. \f(CW0.0.0.0\fR for \f(CW\*(C`DYNA\*(C'\fR or some hostname like \f(CW\*(C`plugin.is.broken.\*(C'\fR for \f(CW\*(C`DYNC\*(C'\fR, but ideally all possible error conditions have been checked out beforehand. .PP \&\f(CW\*(C`_resolve\*(C'\fR is supplied with a resource number, a result structure your code can use to supply address information to the client, a \f(CW\*(C`client_info_t\*(C'\fR structure giving network information about the querying client, and an \f(CW\*(C`origin\*(C'\fR argument. .PP The resource number and origin will match with earlier \f(CW\*(C`map_res\*(C'\fR calls your plugin received. .PP The \f(CW\*(C`client_info_t\*(C'\fR structure contains the querying \s-1DNS\s0 cache's address as well as optional edns-client-subnet address+mask information. If the mask is zero, there was no (useful) edns-client-subnet information, and the plugin must fall back to using the cache's address. When edns-client-subnet information is present, the edns-client-subnet output \*(L"scope\*(R" mask must be set in the result structure (to zero if the information went unused, or to a specific scope as defined in the edns-client-subnet draft (could be shorter or longer than the client's specified mask)). .PP There is no distinction between A and \s-1AAAA\s0 requests (for that matter, your plugin could be invoked to provide Additional-section addresses for other requested types like \s-1MX\s0 or \s-1SRV\s0). You must answer with all applicable IPv4 and IPv6 addresses on every call. Generally speaking, gdnsd treats A and \s-1AAAA\s0 much like a single RR-set. Both are always included in the additional section when appropriate. In response to a direct query for A or \s-1AAAA,\s0 the daemon returns the queried address \s-1RR\s0 type in the answer section and the other in the additional section. .PP Results are added to the opaque \f(CW\*(C`dyn_result_t*\*(C'\fR via the various \&\f(CW\*(C`gdnsd_result_*()\*(C'\fR calls. .PP The \f(CW\*(C`gdnsd_sttl_t\*(C'\fR return value of the resolve callback is used for your plugin to indicate the up/down state and \s-1TTL\s0 of the response placed in the \f(CW\*(C`dyn_result_t\*(C'\fR, which is used to carry these values upwards through nested meta-plugins (e.g. multifo \-> metafo \-> geoip). .PP The \f(CW\*(C`map_res\*(C'\fR callback may also be called at any time during normal runtime as a result of zonefiles being dynamically reloaded. These should be readonly operations so there shouldn't be any locking concerns. It's important that these calls never fail fatally. Simply log an error and return \-1. .PP At the time of daemon exit, \f(CW\*(C`plugin_foo_exit()\*(C'\fR may be called in developer builds as a hook to e.g. unwind complex runtime memory allocation routines for valgrind verification. It's never called in regular production builds. .SH "THREADING" .IX Header "THREADING" gdnsd uses \s-1POSIX\s0 threads. Only the runtime resolve callbacks \&\f(CW\*(C`plugin_foo_map_res\*(C'\fR and \f(CW\*(C`plugin_foo_resolve\*(C'\fR need to to concern themselves with thread safety. They can and will be called from multiple \s-1POSIX\s0 threads simultaneously for runtime requests. .PP The simplest (but least-performant) way to ensure thread-safety would be to wrap the contents of this function in a pthread mutex. However, for most imaginable cases, it should be trivial to structure your data and code such that this function can be both lock-free and thread-safe. .SH "CORE API DETAILS" .IX Header "CORE API DETAILS" These are the functions exported by the core gdnsd code, which are available for your plugin to call at runtime. They're implemented in a library named \f(CW\*(C`libgdnsd\*(C'\fR, which the gdnsd daemon has already loaded before loading your plugin. You don't need to (and shouldn't) explicitly link against libgdnsd. The interfaces are defined in a set of header files grouped by functionality. Note that in your primary plugin source file which includes \fIgdnsd/plugin.h\fR, all of these header files have already been included for you indirectly. .PP For now, the documentation of these interfaces exists solely in the header files themselves. I'm still trying to sort out how to document them correctly, probably doxygen. .IP "gdnsd/compiler.h" 4 .IX Item "gdnsd/compiler.h" .PD 0 .IP "gdnsd/plugapi.h" 4 .IX Item "gdnsd/plugapi.h" .IP "gdnsd/vscf.h" 4 .IX Item "gdnsd/vscf.h" .IP "gdnsd/net.h" 4 .IX Item "gdnsd/net.h" .IP "gdnsd/misc.h" 4 .IX Item "gdnsd/misc.h" .IP "gdnsd/log.h" 4 .IX Item "gdnsd/log.h" .IP "gdnsd/mon.h" 4 .IX Item "gdnsd/mon.h" .IP "gdnsd/dname.h" 4 .IX Item "gdnsd/dname.h" .PD .SH "GENERAL PLUGIN CODING CONVENTIONS, ETC" .IX Header "GENERAL PLUGIN CODING CONVENTIONS, ETC" .IP "logging and errors" 4 .IX Item "logging and errors" All syslog/stderr \-type output should be handled via the thread-safe \&\f(CW\*(C`log_*()\*(C'\fR and \f(CW\*(C`logf_*()\*(C'\fR calls provided by gdnsd. Do not attempt to use stderr (or stdout/stdin) or syslog directly. To throw a fatal error and abort daemon execution, use \f(CW\*(C`log_fatal()\*(C'\fR, which does not return. .IP "debugging" 4 .IX Item "debugging" Build your plugin with \f(CW\*(C`\-DNDEBUG\*(C'\fR unless you're actually debugging development code, and make liberal use of \f(CW\*(C`dmn_assert()\*(C'\fR and \&\f(CW\*(C`log_debug()\*(C'\fR where applicable. .IP "prototypes and headers" 4 .IX Item "prototypes and headers" You do not declare function prototypes for the callback functions (plugin_foo_*). The prototypes are declared for you when you include the \fIgdnsd/plugin.h\fR header. You need merely define the functions themselves. .IP "\s-1API\s0 versioning" 4 .IX Item "API versioning" There is an internal \s-1API\s0 version number documented at the top of this document and set in \f(CW\*(C`gdnsd/plugapi.h\*(C'\fR. This number is only incremented when incompatible changes are made to the plugin \s-1API\s0 interface or semantics which require recompiling plugins and/or updating their code. When gdnsd is compiled this version number is hardcoded into the daemon binary. When plugins are compiled the \s-1API\s0 version they were built against is also hardcoded into the plugin object automatically. When gdnsd loads a plugin object, it checks for an exact match of plugin \s-1API\s0 version. If the number does not match, a fatal error will be thrown telling the user the plugin needs to be rebuilt against the gdnsd version in use. .Sp The current \s-1API\s0 version number is available to your code as the macro \&\f(CW\*(C`GDNSD_PLUGIN_API_VERSION\*(C'\fR. If necessary, you can test this value via \&\f(CW\*(C`#if\*(C'\fR macro logic to use alternate code for different \s-1API\s0 versions (or simply to error out if the \s-1API\s0 version is too old for your plugin code). .IP "map_res consistency" 4 .IX Item "map_res consistency" The \f(CW\*(C`_map_res()\*(C'\fR callback, if implemented, \fBmust\fR return a consistent, singular resource number for a given resource name, regardless of any \&\f(CW\*(C`origin\*(C'\fR argument or the lack thereof. .IP "ignoring origin for address-only data" 4 .IX Item "ignoring origin for address-only data" If a plugin only handles addresses (for this resource, or in the general case), it should not fail on \f(CW\*(C`_map_res()\*(C'\fR or \f(CW\*(C`_resolve()\*(C'\fR just because an origin is defined, indicating a \f(CW\*(C`DYNC\*(C'\fR \s-1RR.\s0 It should instead simply ignore any origin argument and act as it always did. .IP "map_res \s-1DYNA\s0 validity checks" 4 .IX Item "map_res DYNA validity checks" If a resource name passed to \f(CW\*(C`_map_res()\*(C'\fR is configured to be capable of returning \f(CW\*(C`CNAME\*(C'\fR data and the \f(CW\*(C`origin\*(C'\fR argument is \f(CW\*(C`NULL\*(C'\fR (indicating a \f(CW\*(C`DYNA\*(C'\fR \s-1RR\s0), the plugin \fBmust\fR fail by returning \-1. One of the implications of this rule is that for any plugin which is capable of returning \f(CW\*(C`CNAME\*(C'\fR data at all, \f(CW\*(C`_map_res()\*(C'\fR \fBmust\fR be implemented. Another implication of this (combined with the consistency rule) is that it's no longer legal to structure plugin resources such that they have unrelated sets of address and \f(CW\*(C`CNAME\*(C'\fR data stored under the same resource name, as the weighted plugin originally did before its matching set of changes. .SH "RECENT API CHANGES" .IX Header "RECENT API CHANGES" .SS "Version 17" .IX Subsection "Version 17" This corresponds with the release of 2.2.0 .PP Changes versus version 16: .PP \&\f(CW\*(C`gdnsd_dname_isparentof()\*(C'\fR removed (can be trivially replaced using \&\f(CW\*(C`gdnsd_dname_isinzone()\*(C'\fR if necessary). .PP The \s-1PRNG\s0 interfaces have changed completely. The old interface returned a \&\f(CW\*(C`gdnsd_rstate_t*\*(C'\fR from the call \f(CW\*(C`gdnsd_rand_init()\*(C'\fR, which could then be passed to either of \f(CW\*(C`gdnsd_rand_get32()\*(C'\fR or \f(CW\*(C`gdnsd_rand_get64()\*(C'\fR to get unsigned 32\-bit or 64\-bit random numbers, respectively. The replacement interface has split the 32\-bit and 64\-bit random number generators into separate interfaces and state structures. .PP For a 32\-bit \s-1PRNG,\s0 call \f(CW\*(C`gdnsd_rand32_init()\*(C'\fR which returns a \&\f(CW\*(C`gdnsd_rstate32_t*\*(C'\fR, which can then be passed to \f(CW\*(C`gdnsd_rand32_get()\*(C'\fR to obtain unsigned 32\-bit random numbers. .PP For a 64\-bit \s-1PRNG,\s0 call \f(CW\*(C`gdnsd_rand64_init()\*(C'\fR which returns a \&\f(CW\*(C`gdnsd_rstate64_t*\*(C'\fR, which can then be passed to \f(CW\*(C`gdnsd_rand64_get()\*(C'\fR to obtain unsigned 64\-bit random numbers. .SS "Version 15/16" .IX Subsection "Version 15/16" This corresponds with the release of 2.0.0 and 2.1.0 .PP The changes below are versus Version 12 (final gdnsd 1.x \s-1API\s0 version). Versions 13 and 14 only existed in development releases and were moving targets. The changes from Version 12 were rather sweeping. This tries to cover the largest notable changes in the key callbacks, but likely doesn't note them all. When in doubt, look at the source of the core plugins distributed with the main source for guidance. .PP The data structures \f(CW\*(C`dynaddr_result_t\*(C'\fR and \f(CW\*(C`dyncname_result_t\*(C'\fR were merged and replaced with a single structure \f(CW\*(C`dyn_result_t\*(C'\fR, which is an opaque data structure modified by the various \f(CW\*(C`gdnsd_result_*()\*(C'\fR functions for adding or clearing address and/or \s-1CNAME\s0 results. .PP The \f(CW\*(C`_map_res_dyna()\*(C'\fR and \f(CW\*(C`_map_res_dync()\*(C'\fR callbacks were merged and renamed to just \f(CW\*(C`_map_res()\*(C'\fR. The new call has an origin argument like the old \f(CW\*(C`_map_res_dync()\*(C'\fR, which will be \s-1NULL\s0 when called for \&\f(CW\*(C`DYNA\*(C'\fR RRs, and the \f(CW\*(C`result\*(C'\fR argument's type was changed from \f(CW\*(C`dynaddr_result_t*\*(C'\fR to \f(CW\*(C`dyn_result_t*\*(C'\fR. .PP The \f(CW\*(C`_resolve_dynaddr()\*(C'\fR and \f(CW\*(C`_resolve_dyncname()\*(C'\fR callbacks were merged and renamed to just \f(CW\*(C`_resolve()\*(C'\fR. The new call has an origin argument like the old \f(CW\*(C`_resolve_dyncame()\*(C'\fR, which will be \s-1NULL\s0 when called for \f(CW\*(C`DYNA\*(C'\fR RRs, and the \f(CW\*(C`result\*(C'\fR argument's type was changed from \f(CW\*(C`dynaddr_result_t*\*(C'\fR to \f(CW\*(C`dyn_result_t*\*(C'\fR. The new call also lacks the \f(CW\*(C`threadnum\*(C'\fR argument, as any plugin which needs this information can work around it via the \f(CW\*(C`_iothread_init()\*(C'\fR callback and/or thread-local storage. .PP \&\f(CW\*(C`gdnsd_dynaddr_add_result_anysin()\*(C'\fR was renamed to \&\f(CW\*(C`gdnsd_dyn_add_result_anysin()\*(C'\fR, and the \f(CW\*(C`result\*(C'\fR argument's type was changed from \f(CW\*(C`dynaddr_result_t*\*(C'\fR to \f(CW\*(C`dyn_result_t*\*(C'\fR. .PP \&\f(CW\*(C`_load_config()\*(C'\fR no longer has a \f(CW\*(C`mon_list_t*\*(C'\fR return value; instead monitored resources are indicated to the core via the \f(CW\*(C`gdnsd_mon_addr()\*(C'\fR and \f(CW\*(C`gdnsd_mon_cname()\*(C'\fR functions during the execution of \f(CW\*(C`_load_config()\*(C'\fR. .PP Resolver plugins must now call \f(CW\*(C`gdnsd_dyn_addr_max()\*(C'\fR during \f(CW\*(C`_load_config()\*(C'\fR to inform the core of address limits. .PP \&\f(CW\*(C`gdnsd_add_monitor\*(C'\fR was replaced by \f(CW\*(C`gdnsd_add_mon_addr()\*(C'\fR and \&\f(CW\*(C`gdnsd_add_mon_cname()\*(C'\fR. .PP The callbacks \f(CW\*(C`_post_daemonize()\*(C'\fR, \f(CW\*(C`_pre_privdrop()\*(C'\fR, \f(CW\*(C`_post_privdrop()\*(C'\fR, and \f(CW\*(C`_full_config()\*(C'\fR were removed. With the current structure, code that logically fit in these can be placed elsewhere (e.g. \f(CW\*(C`_start_monitors()\*(C'\fR, \f(CW\*(C`_pre_run()\*(C'\fR, or \&\f(CW\*(C`_load_config()\*(C'\fR as appropriate). .PP The type \f(CW\*(C`vscf_data_t*\*(C'\fR in callback arguments used to be \f(CW\*(C`const\*(C'\fR, and now it is not. Similar changes occurred in many places in the vscf \s-1API\s0 in general. Just remove const from your plugin's local vscf pointers and recompile. .PP Version 16 was bumped just to require a recompile (some formerly-exported funcs became inlines, some const changes in signatures, etc), but is mostly the same as 15 otherwise. .SH "SEE ALSO" .IX Header "SEE ALSO" The source for the included addr/cname\-resolution plugins \f(CW\*(C`null\*(C'\fR, \&\f(CW\*(C`reflect\*(C'\fR, \f(CW\*(C`static\*(C'\fR, \f(CW\*(C`simplefo\*(C'\fR, \f(CW\*(C`multifo\*(C'\fR, \f(CW\*(C`weighted\*(C'\fR, \f(CW\*(C`metafo\*(C'\fR, and \f(CW\*(C`geoip\*(C'\fR. The source for the included monitoring plugins \&\f(CW\*(C`http_status\*(C'\fR, \f(CW\*(C`tcp_connect\*(C'\fR, \f(CW\*(C`extmon\*(C'\fR, and \f(CW\*(C`extfile\*(C'\fR. .PP \&\fBgdnsd\fR\|(8), \fBgdnsd.config\fR\|(5), \fBgdnsd.zonefile\fR\|(5) .PP The gdnsd manual. .SH "COPYRIGHT AND LICENSE" .IX Header "COPYRIGHT AND LICENSE" Copyright (c) 2014 Brandon L Black .PP This file is part of gdnsd. .PP gdnsd is free software: you can redistribute it and/or modify it under the terms of the \s-1GNU\s0 General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. .PP gdnsd is distributed in the hope that it will be useful, but \s-1WITHOUT ANY WARRANTY\s0; without even the implied warranty of \&\s-1MERCHANTABILITY\s0 or \s-1FITNESS FOR A PARTICULAR PURPOSE.\s0 See the \&\s-1GNU\s0 General Public License for more details. .PP You should have received a copy of the \s-1GNU\s0 General Public License along with gdnsd. If not, see .