Provided by: gdnsd-dev_2.3.0-1_all bug

NAME

       gdnsd-plugin-api - How to write gdnsd plugin code

SYNOPSIS

         Mandatory preamble macro+header your source must include at the top:
           #define GDNSD_PLUGIN_NAME foo
           #include <gdnsd/plugin.h>

         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 'checkconf', 'start', 'restart', 'condrestart' 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 'start', 'restart', and 'condrestart' 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't 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)

WARNING

       Please note that in general, gdnsd's plugin API 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!).

OVERVIEW

       This file documents versions 15-16 of the gdnsd plugin API.

       gdnsd's plugin API offers the ability to write plugins that can do either (or both) of two
       roles:

       1) Dynamically generate virtual "A", "AAAA", and/or "CNAME" 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
       "DYNA" (for address-only data) and "DYNC" (for which the plugin can return "CNAME" or
       address results).

       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.

       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 "types"
       internally.

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.

       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
       "plugins" 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 "foo" and "bar", the plugins hash might look like this:

         plugins => {
           foo => {
              opts => {
                 something = "quux\000<-an_embedded_null!",
                 somethingelse = { Z => z },
              },
              xyz = [x, y, z]
           }
           bar => { x => y }
         }

       Note that a short-form plugin name (e.g. "foo") maps to a shared library named
       plugin_foo.so.  Plugins will be loaded from the directory /usr/lib/x86_64-linux-gnu/gdnsd
       by default, but this path can be overridden in the "options" section of the gdnsd
       configuration.

       The basic syntactic structure of your plugin's config hash follows the same rules as the
       gdnsd config as a whole.  This is the "vscf" 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.

       The second part of the configuration is inserting "DYNA" and/or "DYNC" resource records
       into zonefiles.  "DYNA" RRs use a plugin to dynamically generate "A" and/or "AAAA" RRs,
       while "DYNC" RRs use a plugin to dynamically generate either "A"/"AAAA" RRs or "CNAME"
       RRs.

         www      300 DYNA foo!prod_web
         www.test 300 DYNA foo!test_web
         web      300 DYNC bar!test_web_cname

       The initial parts (the left-hand domainname, TTL, and RR-type) follow the usual zonefile
       norms, other than the fact that "DYNA" is not a real resource record type in the DNS
       protocol.  The rdata section (e.g.  "foo!prod_web") contains two parts separated by an
       "!": A plugin name, and a resource name.

       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.

       Plugins may implement just address results, just CNAME results, or both.

USER-LEVEL CONFIGURATION FOR MONITORING

       DYNA/DYNC plugin code can optionally take advantage of monitoring services, e.g. to not
       return "dead" addresses from a pool.  Monitoring is configured as a set of
       "service_types", each representing a protocol, protocol-specific parameters, and some
       generic parameters related to timing and anti-flap.  e.g.:

           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
               }
           }

       A service type is meant to be re-used to monitor the same service at several different
       addresses or CNAMEs.

       One of the service type parameters is "plugin", naming a custom monitoring plugin to load.
       If this plugin was not listed directly in the "plugins" hash to give it global-level
       configuration, it will be loaded with no configuration at all ("_load_config(NULL)").

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:

           #define GDNSD_PLUGIN_NAME foo
           #include <gdnsd/plugin.h>

       If you wish to split your implementation over multiple files, you can access the relevant
       API interfaces via the other "gdnsd/*.h" headers directly.  However all of the actual
       callback hooks must be implemented in the primary source file, and your other source files
       should not include "gdnsd/plugin.h".

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 "foo" for documentation purposes.  A brief
       summary of all of the API interfaces and semantics follows in a later section, but it
       would be good to read through this lengthy prose explanation at least once.

   CONFIGURATION
       When gdnsd is started via actions such as "start", "restart" or "condrestart", or when
       configuration is checked via "checkconf", at least some of the plugin callbacks will be
       executed.

       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
       "plugins" hash, it will load and configure the named plugins.  Immediately after loading
       each plugin, it will execute the "plugin_foo_load_config()" 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.

       There are 3 special API calls that are only valid during the execution of
       "plugin_foo_load_config()" and only by resolver plugins, which are used by the plugin to
       feed some configuration-based data back to the core code.  These are "gdnsd_mon_addr()"
       and "gdnsd_mon_cname()" (which are used by resolver plugins to ask the monitoring system
       to monitor addresses and/or CNAMEs), and "gdnsd_dyn_addr_max()", 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 "gdnsd_dyn_addr_max()" results in the core
       assuming a maximum of 1 address per family.

       Next, "service_types" are processed from the config.  These may autoload additional
       plugins that were not specified in the "plugins" hash.  They will also receive a
       "plugin_foo_load_config(NULL)" call if autoloaded.

       For each service type that uses a given plugin, the plugin will receive a
       "plugin_foo_add_svctype()" callback.  Use this to set up local data structures for each
       service type you've been assigned.

       Next, all of the specific monitoring requested earlier by resolver plugins (via
       "gdnsd_mon_addr()" and "gdnsd_mon_cname()") is passed to the monitoring plugins by
       invoking their "plugin_foo_add_mon_addr()" and "plugin_foo_add_mon_cname()".  This is when
       a monitoring plugin sets up per-address/CNAME data structures.

       After all of the above, the daemon loads and parses all zonefiles, constructing the
       internal runtime DNS database.  During the zonefile loading phase, when it encounters
       "DYNA" RRs in zonefiles, they will trigger the plugin callback "plugin_foo_map_res" once
       for every "DYNA" RR, with a "NULL" "origin" argument.  The same occurs with all "DYNC"
       RRs, and they will get non-"NULL" "origin" arguments, which indicate the current $ORIGIN
       in effect for the RR.  It is important to note that your plugin should treat it as an
       error if it gets a "_map_res" call with a "NULL" "origin" (DYNA) for a resource which is
       configured to be capable of returning "CNAME" results.

       If your DYNC 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 RR), 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.

       Plugins should not return different resource numbers for the same resname argument
       regardless of "origin" value (or lack thereof).  You will break things if you do so.

       If your map_resource operation fails (e.g. unknown resource name, or illegal origin-based
       "CNAME" construction, or a NULL origin argument (DYNA) for a resource that could return
       "CNAME" data), log the error and return -1.  Do not fail fatally, as these calls happen at
       runtime during dynamic zonefile reloads.

       In the case of the action "checkconf", execution stops here.  Only the "start" and
       "restart" actions continue on to become full-fledged daemon instances.

       The first is "plugin_foo_init_monitors()".  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
       init_monitors(), 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.

       The next is "plugin_foo_start_monitors()".  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.

       When your libev monitoring callbacks have determined a success or failure for a monitored
       resource, they're expected to call the helper function "gdnsd_mon_state_updater()" from
       gdnsd/mon.h to send the state info upstream for anti-flap calculations and re-destribution
       to plugins which are monitoring the given resource.

       "plugin_foo_pre_run" is executed next, giving a final chance to run any single-threaded
       setup code before threads are spawned and we enter runtime operations.

       After pre_run, gdnsd will spawn the runtime DNS I/O threads.  For each such thread, the
       callback "plugin_foo_iothread_init" 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 "plugin_foo_load_config()" time).  This would be the ideal time to
       xmalloc() writable per-thread data structures from within the threads themselves, so that
       a thread-aware malloc can avoid false sharing.

   RUNTIME
       At this point, gdnsd is ready to begin serving DNS 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.

       During runtime the only direct callbacks your plugin will receive from I/O thread contexts
       are "plugin_foo_resolve" and "plugin_foo_map_res".

       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. 0.0.0.0 for "DYNA" or some hostname like
       "plugin.is.broken." for "DYNC", but ideally all possible error conditions have been
       checked out beforehand.

       "_resolve" is supplied with a resource number, a result structure your code can use to
       supply address information to the client, a "client_info_t" structure giving network
       information about the querying client, and an "origin" argument.

       The resource number and origin will match with earlier "map_res" calls your plugin
       received.

       The "client_info_t" structure contains the querying DNS 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 "scope" 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)).

       There is no distinction between A and AAAA requests (for that matter, your plugin could be
       invoked to provide Additional-section addresses for other requested types like MX or SRV).
       You must answer with all applicable IPv4 and IPv6 addresses on every call.  Generally
       speaking, gdnsd treats A and AAAA 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 AAAA, the
       daemon returns the queried address RR type in the answer section and the other in the
       additional section.

       Results are added to the opaque "dyn_result_t*" via the various "gdnsd_result_*()" calls.

       The "gdnsd_sttl_t" return value of the resolve callback is used for your plugin to
       indicate the up/down state and TTL of the response placed in the "dyn_result_t", which is
       used to carry these values upwards through nested meta-plugins (e.g. multifo -> metafo ->
       geoip).

       The "map_res" 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.

       At the time of daemon exit, "plugin_foo_exit()" 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.

THREADING

       gdnsd uses POSIX threads.  Only the runtime resolve callbacks "plugin_foo_map_res" and
       "plugin_foo_resolve" need to to concern themselves with thread safety.  They can and will
       be called from multiple POSIX threads simultaneously for runtime requests.

       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.

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 "libgdnsd", 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 gdnsd/plugin.h, all of these header files have already been included for you
       indirectly.

       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.

       gdnsd/compiler.h
       gdnsd/plugapi.h
       gdnsd/vscf.h
       gdnsd/net.h
       gdnsd/misc.h
       gdnsd/log.h
       gdnsd/mon.h
       gdnsd/dname.h

GENERAL PLUGIN CODING CONVENTIONS, ETC

       logging and errors
           All syslog/stderr -type output should be handled via the thread-safe "log_*()" and
           "logf_*()" 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
           "log_fatal()", which does not return.

       debugging
           Build your plugin with "-DNDEBUG" unless you're actually debugging development code,
           and make liberal use of "dmn_assert()" and "log_debug()" where applicable.

       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 gdnsd/plugin.h header.  You need
           merely define the functions themselves.

       API versioning
           There is an internal API version number documented at the top of this document and set
           in "gdnsd/plugapi.h".  This number is only incremented when incompatible changes are
           made to the plugin API 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 API 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 API 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.

           The current API version number is available to your code as the macro
           "GDNSD_PLUGIN_API_VERSION".  If necessary, you can test this value via "#if" macro
           logic to use alternate code for different API versions (or simply to error out if the
           API version is too old for your plugin code).

       map_res consistency
           The "_map_res()" callback, if implemented, must return a consistent, singular resource
           number for a given resource name, regardless of any "origin" argument or the lack
           thereof.

       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 "_map_res()" or "_resolve()" just because an origin is defined,
           indicating a "DYNC" RR.  It should instead simply ignore any origin argument and act
           as it always did.

       map_res DYNA validity checks
           If a resource name passed to "_map_res()" is configured to be capable of returning
           "CNAME" data and the "origin" argument is "NULL" (indicating a "DYNA" RR), the plugin
           must fail by returning -1.  One of the implications of this rule is that for any
           plugin which is capable of returning "CNAME" data at all, "_map_res()" must 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 "CNAME" data stored under the same resource name, as the weighted
           plugin originally did before its matching set of changes.

RECENT API CHANGES

   Version 17
       This corresponds with the release of 2.2.0

       Changes versus version 16:

       "gdnsd_dname_isparentof()" removed (can be trivially replaced using
       "gdnsd_dname_isinzone()" if necessary).

       The PRNG interfaces have changed completely.  The old interface returned a
       "gdnsd_rstate_t*" from the call "gdnsd_rand_init()", which could then be passed to either
       of "gdnsd_rand_get32()" or "gdnsd_rand_get64()" 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.

       For a 32-bit PRNG, call "gdnsd_rand32_init()" which returns a "gdnsd_rstate32_t*", which
       can then be passed to "gdnsd_rand32_get()" to obtain unsigned 32-bit random numbers.

       For a 64-bit PRNG, call "gdnsd_rand64_init()" which returns a "gdnsd_rstate64_t*", which
       can then be passed to "gdnsd_rand64_get()" to obtain unsigned 64-bit random numbers.

   Version 15/16
       This corresponds with the release of 2.0.0 and 2.1.0

       The changes below are versus Version 12 (final gdnsd 1.x API 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.

       The data structures "dynaddr_result_t" and "dyncname_result_t" were merged and replaced
       with a single structure "dyn_result_t", which is an opaque data structure modified by the
       various "gdnsd_result_*()" functions for adding or clearing address and/or CNAME results.

       The "_map_res_dyna()" and "_map_res_dync()" callbacks were merged and renamed to just
       "_map_res()".  The new call has an origin argument like the old "_map_res_dync()", which
       will be NULL when called for "DYNA" RRs, and the "result" argument's type was changed from
       "dynaddr_result_t*" to "dyn_result_t*".

       The "_resolve_dynaddr()" and "_resolve_dyncname()" callbacks were merged and renamed to
       just "_resolve()".  The new call has an origin argument like the old "_resolve_dyncame()",
       which will be NULL when called for "DYNA" RRs, and the "result" argument's type was
       changed from "dynaddr_result_t*" to "dyn_result_t*".  The new call also lacks the
       "threadnum" argument, as any plugin which needs this information can work around it via
       the "_iothread_init()" callback and/or thread-local storage.

       "gdnsd_dynaddr_add_result_anysin()" was renamed to "gdnsd_dyn_add_result_anysin()", and
       the "result" argument's type was changed from "dynaddr_result_t*" to "dyn_result_t*".

       "_load_config()" no longer has a "mon_list_t*" return value; instead monitored resources
       are indicated to the core via the "gdnsd_mon_addr()" and "gdnsd_mon_cname()" functions
       during the execution of "_load_config()".

       Resolver plugins must now call "gdnsd_dyn_addr_max()" during "_load_config()" to inform
       the core of address limits.

       "gdnsd_add_monitor" was replaced by "gdnsd_add_mon_addr()" and "gdnsd_add_mon_cname()".

       The callbacks "_post_daemonize()", "_pre_privdrop()", "_post_privdrop()", and
       "_full_config()" were removed.  With the current structure, code that logically fit in
       these can be placed elsewhere (e.g. "_start_monitors()", "_pre_run()", or "_load_config()"
       as appropriate).

       The type "vscf_data_t*" in callback arguments used to be "const", and now it is not.
       Similar changes occurred in many places in the vscf API in general.  Just remove const
       from your plugin's local vscf pointers and recompile.

       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.

SEE ALSO

       The source for the included addr/cname-resolution plugins "null", "reflect", "static",
       "simplefo", "multifo", "weighted", "metafo", and "geoip".  The source for the included
       monitoring plugins "http_status", "tcp_connect", "extmon", and "extfile".

       gdnsd(8), gdnsd.config(5), gdnsd.zonefile(5)

       The gdnsd manual.

COPYRIGHT AND LICENSE

       Copyright (c) 2014 Brandon L Black <blblack@gmail.com>

       This file is part of gdnsd.

       gdnsd is free software: you can redistribute it and/or modify it under the terms of the
       GNU General Public License as published by the Free Software Foundation, either version 3
       of the License, or (at your option) any later version.

       gdnsd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
       even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       GNU General Public License for more details.

       You should have received a copy of the GNU General Public License along with gdnsd.  If
       not, see <http://www.gnu.org/licenses/>.