=head1 NAME

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

=head1 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)

=head1 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!).

=head1 OVERVIEW

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

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

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

=head1 USER-LEVEL CONFIGURATION FOR RESOLVER PLUGINS

If you haven't read the documentation for the overall configuration
file (L<gdnsd.config>) and the zonefiles (L<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 C<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 C<foo> and C<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. C<foo>) maps to a shared
library named F<plugin_foo.so>.  Plugins will be loaded from the
directory F<@GDNSD_DEFPATH_LIB@> by default, but this path can be
overridden in the C<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 C<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 C<DYNA> and/or
C<DYNC> resource records into zonefiles.  C<DYNA> RRs use a plugin to
dynamically generate C<A> and/or C<AAAA> RRs, while C<DYNC> RRs use a
plugin to dynamically generate either C<A>/C<AAAA> RRs or C<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 C<DYNA> is not a
real resource record type in the DNS protocol.  The rdata section (e.g.
C<foo!prod_web>) contains two parts separated by an C<!>: 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.

=head1 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 C<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 C<plugin>, naming a custom
monitoring plugin to load.  If this plugin was not listed directly in
the C<plugins> hash to give it global-level configuration, it will be
loaded with no configuration at all (C<_load_config(NULL)>).

=head1 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 C<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 B<not>
include C<gdnsd/plugin.h>.

=head1 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 L<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 C<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.

=head2 CONFIGURATION

When gdnsd is started via actions such as C<start>, C<restart> or
C<condrestart>, or when configuration is checked via C<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 C<plugins> hash, it will load and
configure the named plugins.  Immediately after loading each plugin, it
will execute the C<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 C<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 C<gdnsd_mon_addr()> and C<gdnsd_mon_cname()> (which are
used by resolver plugins to ask the monitoring system to monitor addresses
and/or CNAMEs), and C<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 C<gdnsd_dyn_addr_max()>
results in the core assuming a maximum of 1 address per family.

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

For each service type that uses a given plugin, the plugin will receive
a C<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 C<gdnsd_mon_addr()> and C<gdnsd_mon_cname()>) is passed to the
monitoring plugins by invoking their C<plugin_foo_add_mon_addr()> and
C<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 C<DYNA> RRs in zonefiles, they will
trigger the plugin callback C<plugin_foo_map_res> once for
every C<DYNA> RR, with a C<NULL> C<origin> argument.  The same occurs
with all C<DYNC> RRs, and they will get non-C<NULL> C<origin> arguments,
which indicate the current C<$ORIGIN> in effect for the RR.  It is
important to note that your plugin should treat it as an error if
it gets a C<_map_res> call with a C<NULL> C<origin> (DYNA) for a resource
which is configured to be capable of returning C<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 B<not> return different resource numbers for the same
resname argument regardless of C<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 C<CNAME> construction, or a NULL origin argument (DYNA)
for a resource that could return C<CNAME> data), log the error and return -1.
Do B<not> fail fatally, as these calls happen at runtime during dynamic
zonefile reloads.

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

The first is C<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 C<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 C<gdnsd_mon_state_updater()> from F<gdnsd/mon.h> to send the
state info upstream for anti-flap calculations and re-destribution to
plugins which are monitoring the given resource.

C<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 C<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 C<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.

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

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

The resource number and origin will match with earlier C<map_res>
calls your plugin received.

The C<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 C<dyn_result_t*> via the various
C<gdnsd_result_*()> calls.

The C<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 C<dyn_result_t>, which is used to carry these values upwards
through nested meta-plugins (e.g. multifo -> metafo -> geoip).

The C<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, C<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.

=head1 THREADING

gdnsd uses POSIX threads.  Only the runtime resolve callbacks
C<plugin_foo_map_res> and C<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.

=head1 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 C<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 F<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.

=over 4

=item gdnsd/compiler.h

=item gdnsd/plugapi.h

=item gdnsd/vscf.h

=item gdnsd/net.h

=item gdnsd/misc.h

=item gdnsd/log.h

=item gdnsd/mon.h

=item gdnsd/dname.h

=back

=head1 GENERAL PLUGIN CODING CONVENTIONS, ETC

=over 4

=item logging and errors

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

=item debugging

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

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

=item API versioning

There is an internal API version number documented at the top of this
document and set in C<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
C<GDNSD_PLUGIN_API_VERSION>.  If necessary, you can test this value via
C<#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).

=item map_res consistency

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

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

=item map_res DYNA validity checks

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

=back

=head1 RECENT API CHANGES

=head2 Version 17

This corresponds with the release of 2.2.0

Changes versus version 16:

C<gdnsd_dname_isparentof()> removed (can be trivially replaced using
C<gdnsd_dname_isinzone()> if necessary).

The PRNG interfaces have changed completely.  The old interface returned a
C<gdnsd_rstate_t*> from the call C<gdnsd_rand_init()>, which could then be
passed to either of C<gdnsd_rand_get32()> or C<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 C<gdnsd_rand32_init()> which returns a
C<gdnsd_rstate32_t*>, which can then be passed to C<gdnsd_rand32_get()>
to obtain unsigned 32-bit random numbers.

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

=head2 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 C<dynaddr_result_t> and C<dyncname_result_t> were
merged and replaced with a single structure C<dyn_result_t>, which
is an opaque data structure modified by the various C<gdnsd_result_*()>
functions for adding or clearing address and/or CNAME results.

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

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

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

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

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

C<gdnsd_add_monitor> was replaced by C<gdnsd_add_mon_addr()> and
C<gdnsd_add_mon_cname()>.

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

The type C<vscf_data_t*> in callback arguments used to be C<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.

=head1 SEE ALSO

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

L<gdnsd(8)>, L<gdnsd.config(5)>, L<gdnsd.zonefile(5)>

The gdnsd manual.

=head1 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/>.

=cut
