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

FreeBSD Manual Pages

  
 
  

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

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

gdnsd 2.4.3			  2026-02-28	     docs::gdnsd-plugin-api(3)

Want to link to this manual page? Use this URL:
<https://man.freebsd.org/cgi/man.cgi?query=gdnsd-plugin-api&sektion=3&manpath=FreeBSD+Ports+15.0.quarterly>

home | help