focal (3) gdnsd-plugin-api.3.gz

Provided by: gdnsd-dev_2.4.2-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 (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/>.