Skip site navigation (1)Skip section navigation (2)

FreeBSD Manual Pages


home | help
docs::gdnsd-plugin-api(3)	     gdnsd	     docs::gdnsd-plugin-api(3)

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

	 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)

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

       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.

       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  Plugins will be loaded from the directory
       /usr/local/lib/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

       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 =
		   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)").

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

       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.

       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

       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

       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

       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. for "DYNA" or some
       hostname	like "" 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.

       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.

       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.


       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.

	   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.

   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

       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

       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.

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

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

gdnsd 2.4.2			  2020-08-31	     docs::gdnsd-plugin-api(3)


Want to link to this manual page? Use this URL:

home | help