Description of the getdns
API
Paul Hoffman, Editor
Document version: "getdns September 2014"
This document describes a modern asynchronous DNS API. This new API is intended to be useful to application developers and operating system distributors as a way of making all types of DNS information easily available in many types of programs. The major features of this new API are:
getaddrinfo()
There is more background into the design and future goals of this API later in this document.
This document was discussed on the getdns-api mailing list; future versions of the API might be discussed there as well. (If you want to contact the editor off-list, please send mail to paul.hoffman@vpnc.org.)
getdns
Async FunctionsThe API has four async functions:
getdns_address
for doing getaddrinfo()
-like address lookupsgetdns_hostname
for doing getnameinfo()
-like name lookupsgetdns_service
for getting results from SRV lookupsgetdns_general
for looking up any type of DNS recordgetdns_general()
context
A pointer to the DNS context that is to be used with this call. DNS contexts are described later in this document. Note that a context must be created before calling the function.
*name
This is a null-terminted string consisting of an ASCII-based domain name to be looked up. The values here follow the rules in section 2.1 of RFC 4343 to allow non-ASCII octets and special characters in labels.
request_type
Specifies the RRtype for the query; the RRtype numbers are listed in the IANA
registry. For example, to get the NS records, request_type
would be 2. The API also has
defined macros for most of the RRtypes by name; the definition names all start with
"GETDNS_RRTYPE_
". For example, to get the NS records, you can also set the
request_type
to GETDNS_RRTYPE_NS
.
(The full list of request types is always
here.)
*extensions
Specifies the extensions for this request; the value may be NULL if there are no extensions. See the section below for information on how to specify the extensions used for a request.
*userarg
A void* that is passed to the function, which the funciton
returns to the callback function untouched. userarg
can be used by the callback
function for any user-specific data needed. This can be NULL.
*transaction_id
A pointer to a value that is filled in by the
function to identify the callback being made.
This pointer can be NULL, in which case it is ignored and no value is assigned.
The getdns_cancel_callback()
function uses the
transaction_id
to determine which callback is to be cancelled.
If the function fails,
transaction_id
is set to 0.
*callbackfn
A pointer to a callback function that is defined by the application. Typically, the callback function will do all the processing on the results from the API. The parameters of the callback are defined below. This really needs to be a pointer to a function (and not something like NULL); otherwise, the results are unpredictable.
The async getdns
functions return GETDNS_RETURN_GOOD
if the call was properly formatted.
It returns GETDNS_RETURN_BAD_DOMAIN_NAME
if the API determines that the name passed to
the function was bad, GETDNS_RETURN_BAD_CONTEXT
if the context has internal deficiencies,
GETDNS_RETURN_NO_SUCH_EXTENSION
if one or more extensions do not exist, or
GETDNS_RETURN_EXTENSION_MISFORMAT
if the contents of one or more of the
extensions is incorrect. All of the return values are given later in this document.
getdns_address()
There are three critical differences between getdns_address()
and
getdns_general()
beyond the missing request_type
argument:
getdns_address()
, the name
argument can only take a host name.return_both_v4_and_v6
extension with the call in
getdns_address()
: it will always return both IPv4 and IPv6 addresses.getdns_address()
always uses all of namespaces from the context (to better emulate
getaddrinfo()
), while getdns_general()
only uses the DNS namespace.getdns_hostname()
The address is given as a getdns_dict
data structure (defined below). The list must
have two names: address_type
(whose value is a bindata; it is currently either "IPv4"
or "IPv6" (which are case-sensitive)) and address_data
(whose value is a bindata).
getdns_service()
name
must be a domain name for an SRV lookup; the call returns the
relevant SRV information for the name.
getdns
A call to the async getdns
functions typically returns before any network or file I/O occurs. After
the API marshalls all the needed information, it calls the callback function that was passed by the
application. The callback function might be called at any time, even before the calling function
has returned. The API guarantees that the callback will be called exactly once unless the calling function
returned an error, in which case the callback function is never called.
The getdns
calling function calls the callback with the parameters defined
as follows:
context
The DNS context that was used in the calling function. See below for a description of the basic use of contexts, and later for more advanced use.
callback_type
Supplies the reason for the callback. See below for the codes and reasons.
*response
A response object with the response data. This is described below. The application is responsible for cleaning up the response object with getdns_dict_destroy.
*userarg
Identical to the *userarg
passed to the calling function.
transaction_id
The transaction identifier that was assigned by the calling function.
The following are the values for callback_type.
GETDNS_CALLBACK_COMPLETE
The response has the requested data in it
GETDNS_CALLBACK_CANCEL
The calling program cancelled the callback; response is NULL
GETDNS_CALLBACK_TIMEOUT
The requested action timed out; response is filled in with empty structures
GETDNS_CALLBACK_ERROR
The requested action had an error; response is NULL
Calls to getdns
functions require a DNS context, which is a group of API settings
that affect how DNS calls are made. For most applications, a default context is sufficient.
To create a new DNS context, use the function:
The call to getdns_context_create
immediately returns a context that can
be used with other API calls; that context contains the API's default values. Most applications will
want set_from_os
set to 1
.
To clean up the context, including cleaning up all outstanding transactions that were called using this context, use the function:
When getdns_context_destroy()
returns, the
application knows that all outstanding transactions associated with this
context will have been called; callbacks that had not been called before
getdns_context_destroy()
was called will be called with a callback_type of
GETDNS_CALLBACK_CANCEL
. getdns_context_destroy()
returns after
all of the needed cleanup is done and callbacks are made.
To cancel an outstanding callback, use the following function.
This causes the API to call the callback with a callback_type
of
GETDNS_CALLBACK_CANCEL
if the callback for this transaction_id
has not
already been called. This will cancel the callback regardless of what the original call was
doing (such as in the middle of a DNS request, while DNSSEC validation is happening, and so on).
The callback code for cancellation should clean up any memory related to the
identified call, such as to deallocate the memory for the userarg.
getdns_cancel_callback()
may return immediately, even before the callback finishes its
work and returns. Calling getdns_cancel_callback()
with a transaction_id
of a callback that has already been called or an unknown transaction_id
returns
GETDNS_RETURN_UNKNOWN_TRANSACTION
; otherwise, getdns_cancel_callback()
returns GETDNS_RETURN_GOOD
.
Event-driven programs (sometimes called "async programs") require an event
base and event loop (among other things). Different event libraries have
different structures or the event base. Because of this, there is no standard
method to set the event base in the DNS API: those are all added as
extensions. The API is distributed as a core package and one or more sets of
extensions to align with event libraries. It is mandatory to use one of the extension
functions to set the event base in the DNS context; this is required before
calling any event-driven calls like the getdns
functions.
Each implementation of the DNS API will specify an extension function that
tells the DNS context which event base is being used. For example, one
implementation of this API that uses the libevent event library might name
this function "getdns_extension_set_libevent_base()
" while
another might name it
"getdns_extension_set_eventbase_for_libevent()
"; the two
extension functions could have very different calling patterns and return
values. Thus, the application developer must read the API documentation
(not just this design document) in order to determine what extension function
to use to tell the API the event base to use.
The structure of a typical event-driven application might look like the following pseudocode. The code in italics is specific to the event mechanism.
Includes for one or more regular C libraries An include for the getdns library specific to the event library you use Definition of your callback function Get the DNS data from the allocated pointer Process that data Check for errors Definition of main() Create context Set up your event base Point the context to your event base Set up the getdns call arguments Make the getdns call Check if the getdns return is good Destroy the context Exit
The API does not have direct support for a polling interface. Instead, the callback interface is
specifically designed to allow an application that wants to process results in polling instead of in
callbacks to be able to create its own polling interface fairly trivially. Such a program would
create a data structure for the calls, including their transaction_id
and
userarg
. The userarg
could be the polling data structure or have a pointer to it.
The application would have just
one callback function for all requests, and that function would copy the response
into
application memory, update the data structure based on the transaction_id
index,
and return from the callback. The polling code could then check the data structure for any updates
at its leisure.
There are functions parallel to the four getdns
async functions,
except that there is no callback. That is, when an application calls one of these
synchronous functions, the
API gathers all the required information and then returns the result. The value returned is exactly the
same as the response returned in the callback if you had used the async version of the function.
When you are done with the data in the response, use the following function so that the API can free the memory from its internal pool.
The API returns data structures. The data structure is not a representational language like JSON: it is really just a data structure. Data structures can have four types of members:
{ size_t size; uint8_t *binary_stuff; }
.The API comes with helper functions to get data from the list and dict data types:
All of these helper getter functions return GETDNS_RETURN_GOOD
if the call is successful.
The list functions will return GETDNS_RETURN_NO_SUCH_LIST_ITEM
if the index argument is
out of range; the dict functions will return GETDNS_RETURN_NO_SUCH_DICT_NAME
if the name
argument doesn't exist in the dict. The functions also return GETDNS_RETURN_WRONG_TYPE_REQUESTED
if the requested data type doesn't match the contents of the indexed argument or name.
This document uses graphical representations of data structures. It is important to note that this is only a graphical representation; the brackets, commas, quotation marks, comments, and so on are not part of the data. Also, this document uses macro names instead of some of the int arguments; of course, the data structures have the actual int in them.
The helper getter functions return references to getdns_dict
, getdns_list
and getdns_bindata
data structures.
The user must not directly destroy these retrieved "child" data structures; instead,
they will automatically be destroyed when the containing "parent" data structure is destroyed.
Because of this, retrieved "child" data structures cannot be used any more after the containing "parent" data structure has been destroyed.
Some of the features of the API require that you create your own data structures to be used in arguments passed to the API. For example, if you want to use any extensions for the calling functions, you need to create a dict. The requisite functions are:
Lists are extended with the getdns_list_set_
calls with the index
set to the
size of the list (such as 0 for an empty list). Dicts are extended with the getdns_dict_set_
calls
with the name
set to a name that does not yet exist. Name-value pairs are removed with
getdns_dict_remove_name()
.
These helper setter functions return GETDNS_RETURN_GOOD
if the call is successful.
The functions return GETDNS_RETURN_WRONG_TYPE_REQUESTED
if the requested data type
doesn't match the contents of the indexed argument or name. The list functions will return
GETDNS_RETURN_NO_SUCH_LIST_ITEM
if the index argument is higher than the length of the
list. getdns_dict_remove_name()
will return
GETDNS_RETURN_NO_SUCH_DICT_NAME
if the name argument doesn't exist in the dict.
The helper setter functions store copies of the given "child" values. It is the responsibility of the caller to dispose of the original values.
Extensions are dict data structures. The names in the dict are the names of the extensions.
The definition of each extension describes the value associated with the name. For most extensions,
it is an on-off boolean, and the value is GETDNS_EXTENSION_TRUE
. (There is
not currently a good reason to specify an extension name and give it a value of GETDNS_EXTENSION_FALSE
,
but that is allowed by the API.)
For example, to create a dict for extensions and specify the extension to only return results that have been validated with DNSSEC, you might use:
/* . . . */ getdns_dict * this_extensions = getdns_dict_create(); this_ret = getdns_dict_set_int(this_extensions, "dnssec_return_only_secure", GETDNS_EXTENSION_TRUE); /* . . . Do some processing with the extensions and results . . . */ /* Remember to clean up memory*/ getdns_dict_destroy(this_extensions);
The extensions described in this section are are:
dnssec_return_status
dnssec_return_only_secure
dnssec_return_validation_chain
return_both_v4_and_v6
add_opt_parameters
add_warning_for_bad_dns
specify_class
return_call_debugging
If an application wants the API to do DNSSEC validation for a request, it must set one or more DNSSEC-related extensions. Note that the default is for none of these extensions to be set and the API will not perform DNSSEC. Note that getting DNSSEC results can take longer in a few circumstances.
To return the DNSSEC status for each DNS record in the replies_tree
list, use the
dnssec_return_status
extension. The extension's value (an int) is set to
GETDNS_EXTENSION_TRUE
to cause the returned status to have the name
dnssec_status
(an int) added to the other names in the record's dict ("header",
"question", and so on). The values for that name are GETDNS_DNSSEC_SECURE
,
GETDNS_DNSSEC_BOGUS
, GETDNS_DNSSEC_INDETERMINATE
, and
GETDNS_DNSSEC_INSECURE
. Thus, a reply might look like:
{ # This is the first reply "dnssec_status": GETDNS_DNSSEC_INDETERMINATE, "header": { "id": 23456, "qr": 1, "opcode": 0, ... }, . . .
If instead of returning the status, you want to only see secure results, use the
dnssec_return_only_secure
extension. The extension's value (an int) is set to
GETDNS_EXTENSION_TRUE
to cause only records that the API can validate as secure with
DNSSEC to be returned in the replies_tree
and replies_full
lists. No
additional names are added to the dict of the record; the change is that some records might not
appear in the results. When this context option is set, if the API receives DNS replies but none
are determined to be secure, the error code at the top level of the response object is
GETDNS_RESPSTATUS_NO_SECURE_ANSWERS
.
Applications that want to do their own validation will want to have the DNSSEC-related records
for a particular response. Use the dnssec_return_validation_chain
extension. The
extension's value (an int) is set to GETDNS_EXTENSION_TRUE
to cause a set
of additional DNSSEC-related records needed for validation to be returned in the response object.
This set comes as validation_chain
(a list) at the top level of the response object.
This list includes all resource record dicts for all the resource records (DS, DNSKEY and their RRSIGs) that are needed to perform the validation from the root up. Thus, a reply might look like:
{ # This is the response object "validation_chain": [ { "name": <bindata for .>, "type": GETDNS_RRTYPE_DNSKEY, "rdata": { "flags": 256, . . . }, . . . }, { "name": <bindata for .>, "type": GETDNS_RRTYPE_DNSKEY, "rdata": { "flags": 257, . . . }, . . . }, { "name": <bindata for .>, "type": GETDNS_RRTYPE_RRSIG, "rdata": { "signers_name": <bindata for .>, "type_covered": GETDNS_RRTYPE_DNSKEY, . . . }, }, { "name": <bindata for com.>, "type": GETDNS_RRTYPE_DS, . . . }, { "name": <bindata for com.>, "type": GETDNS_RRTYPE_RRSIG "rdata": { "signers_name": <bindata for .>, "type_covered": GETDNS_RRTYPE_DS, . . . }, . . . }, { "name": <bindata for com.>, "type": GETDNS_RRTYPE_DNSKEY "rdata": { "flags": 256, . . . }, . . . }, { "name": <bindata for com.>, "type": GETDNS_RRTYPE_DNSKEY "rdata": { "flags": 257, . . . }, . . . }, { "name": <bindata for com.>, "type": GETDNS_RRTYPE_RRSIG "rdata": { "signers_name": <bindata for com.>, "type_covered": GETDNS_RRTYPE_DNSKEY, . . . }, . . . }, { "name": <bindata for example.com.>, "type": GETDNS_RRTYPE_DS, . . . }, { "name": <bindata for example.com.>, "type": GETDNS_RRTYPE_RRSIG "rdata": { "signers_name": <bindata for com.>, "type_covered": GETDNS_RRTYPE_DS, . . . }, . . . }, { "name": <bindata for example.com.>, "type": GETDNS_RRTYPE_DNSKEY "rdata": { "flags": 257, ... }, . . . }, . . . ] "replies_tree": [ . . .
If a request is using a context in which stub resolution is set, and that request also has
any of the dnssec_return_status
, dnssec_return_only_secure
, or
dnssec_return_validation_chain
extensions specified, the API will not perform
the request and will instead return an error of GETDNS_RETURN_DNSSEC_WITH_STUB_DISALLOWED
.
Many applications want to get both IPv4 and IPv6 addresses in a single call so that the results
can be processed together. The getdns_address
and getdns_address_sync
functions are able to do this automatically. If you are using the getdns_general
or
getdns_general_sync
function, you can enable this with the
return_both_v4_and_v6
extension. The extension's value (an int) is set to
GETDNS_EXTENSION_TRUE
to cause the results to be the lookup of either A or AAAA records
to include any A and AAAA records for the queried name (otherwise, the extension does nothing).
These results are expected to be used with Happy Eyeballs systems that will find the best socket for
an application.
For lookups that need an OPT resource record in the Additional Data section, use the
add_opt_parameters
extension. The extension's value (a dict) contains the
parameters; these are described in more detail in RFC 6891. They are:
maximum_udp_payload_size
(an int), a value between 512 and 65535; if not specified,
this defaults to those from the DNS contextextended_rcode
(an int), a value between 0 and 255; if not specified,
this defaults to those from the DNS contextversion
(an int), a value between 0 and 255; if not specified, this
defaults to 0do_bit
(an int), a value between 0 and 1; if not specified, this defaults
to those from the DNS contextoptions
(a list) contains dicts for each option to be specified.
Each list time contains two names: option_code
(an int) and option_data
(a bindata). The API marshalls the entire set of options into a properly-formatted RDATA
for the resource record.It is very important to note that the OPT resource record specified in the
add_opt_parameters
extension might not be the same the one that the API sends in the
query. For example, if the application also includes any of the DNSSEC extensions, the API will make
sure that the OPT resource record sets the resource record appropriately, making the needed changes
to the settings from the add_opt_parameters
extension.
The use of this extension can conflict with the values in the DNS context. For example, the default for an OS might be a maximum payload size of 65535, but the extension might specify 1550. In such a case, the API will honor the values stated in the extension, but will honor the values from the DNS context if values are not given in the extension.
To receive a warning if a particular response violates some parts of the DNS standard, use
the add_warning_for_bad_dns
extension. The extension's value (an int) is set to
GETDNS_EXTENSION_TRUE
to cause each reply in the replies_tree
to contain an additional name, bad_dns
(a list). The list is zero or more
ints that indicate types of bad DNS found in that reply. The list of values is:
GETDNS_BAD_DNS_CNAME_IN_TARGET
A DNS query type that does not allow a target to be a CNAME pointed to a CNAME
GETDNS_BAD_DNS_ALL_NUMERIC_LABEL
One or more labels in a returned domain name is all-numeric; this is not legal for a hostname
GETDNS_BAD_DNS_CNAME_RETURNED_FOR_OTHER_TYPE
A DNS query for a type other than CNAME returned a CNAME response
The vast majority of DNS requests are made with the Internet (IN) class. To make a request in a
different DNS class, use, the specify_class
extension. The extension's value (an int)
contains the class number. Few applications will ever use this extension.
An application might want to see debugging information for queries such as the length of time it
takes for each query to return to the API. Use the return_call_debugging
extension. The
extension's value (an int) is set to GETDNS_EXTENSION_TRUE
to add the name
call_debugging
(a list) to the top level of the response object. Each member of the
list is a dict that represents one call made for the call to the API. Each member has the following
names:
query_name
(a bindata) is the name that was sentquery_type
(an int) is the type that was queried forquery_to
(a bindata) is the address to which the query was sentstart_time
(a bindata) is the time the query started in milliseconds since the epoch,
represented as a uint64_tend_time
(a bindata) is the time the query was received in milliseconds since the epoch,
represented as a uint64_tentire_reply
(a bindata) is the entire response receiveddnssec_result
(an int) is the DNSSEC status, or GETDNS_DNSSEC_NOT_PERFORMED
if DNSSEC validation was not performedThe callback function contains a pointer to a response object.
A response object is always a dict. The response
object always contains at least three names: replies_full
(a list) and
replies_tree
(a list), and status
(an int).
replies_full
is a list of DNS replies (each is bindata) as they appear on the wire.
replies_tree
is a list of DNS replies (each is a dict) with the various part of the
reply parsed out. status
is a status code for the query.
Because the API might be extended in the future, a response object might also contain names other
than replies_full
, replies_tree
, and status
. Similarly, any
of the dicts described here might be extended in later versions of the API. Thus, an application
using the API must not assume that it knows all possible names in a dict.
The following lists the status codes for response objects. Note that, if the status is that there
are no responses for the query, the lists in replies_full
and replies_tree
will have zero length.
GETDNS_RESPSTATUS_GOOD
At least one response was returned
GETDNS_RESPSTATUS_NO_NAME
Queries for the name yielded all negative responses
GETDNS_RESPSTATUS_ALL_TIMEOUT
All queries for the name timed out
GETDNS_RESPSTATUS_NO_SECURE_ANSWERS
The context setting for getting only secure responses was specified, and at least one DNS response was received, but no DNS response was determined to be secure through DNSSEC.
GETDNS_RESPSTATUS_ALL_BOGUS_ANSWERS
The context setting for getting only secure responses was specified, and at least one DNS response was received, but all received responses for the requested name were bogus.
The top level of replies_tree
can optionally have the following names: canonical_name
(a
bindata), intermediate_aliases
(a list), answer_ipv4_address
(a bindata),
answer_ipv6_address
(a bindata), and answer_type
(an int).
canonical_name
is the name that the API used for its lookup. It is in
FQDN presentation format.intermediate_aliases
list are domain names from any CNAME or
unsynthesized DNAME found when resolving the original query. The list might have zero entries
if there were no CNAMEs in the path. These may be useful, for example, for name comparisons
when following the rules in RFC 6125.answer_ipv4_address
and answer_ipv6_address
are
the addresses of the server from which the answer was received.answer_type
is the type of name service that generated the response.
The values are:GETDNS_NAMETYPE_DNS
Normal DNS (RFC 1035)
GETDNS_NAMETYPE_WINS
The WINS name service (some reference needed)
If the call was getdns_address
or getdns_address_sync
, the top level
of replies_tree
has an additional name, just_address_answers
(a list).
The value of just_address_answers
is a list that contains all of the A and AAAA
records from the answer sections of any of the replies, in the order they appear in the replies.
Each item in the list is a dict with at least two names: address_type
(whose value is
a bindata; it is currently either "IPv4" or "IPv6") and address_data
(whose value is a bindata).
Note that the dnssec_return_only_secure
extension affects
what will appear in the just_address_answers
list. Also note if later versions of the
DNS return other address types, those types will appear in this list as well.
The API can make service discovery through SRV records easier. If
the call was getdns_service
or getdns_service_sync
,
the top level of replies_tree
has an additional name,
srv_addresses
(a list).
The list is ordered by priority and weight based on the weighting
algorithm in RFC 2782, lowest priority value first. Each element
of the list is dict has at least two names: port
and domain_name
. If the
API was able to determine the address of the target domain name (such as from its cache or from the
Additional section of responses), the dict for an element will also contain
address_type
(whose value is a bindata; it is currently either "IPv4" or "IPv6") and
address_data
(whose value is a bindata). Note that the
dnssec_return_only_secure
extension affects what will appear in the
srv_addresses
list.
replies_tree
The names in each entry in the the replies_tree
list for DNS responses include
header
(a dict), question
(a dict), answer
(a list),
authority
(a list), and additional
(a list), corresponding to the sections
in the DNS message format. The answer, authority, and additional lists each contain zero or more
dicts, with each dict in each list representing a resource record.
The names in the header
dict are all the fields from Section 4.1.1. of RFC 1035.
They are: id
, qr
, opcode
, aa
, tc
,
rd
, ra
, z
, rcode
, qdcount
,
ancount
, nscount
, and arcount
. All are ints.
The names in the question
dict are the three fields from Section 4.1.2. of RFC 1035:
qname
(a bindata), qtype
(an int), and qclass
(an int).
Resource records are a bit different than headers and question sections in that the RDATA portion
often has its own structure. The other names in the resource record dicts are name
(a
bindata), type
(an int), class
(an int), ttl
(an int) and
rdata
(a dict); there is no name equivalent to the RDLENGTH field.
The OPT
resource record does not have the class
and the ttl
name, but in stead provides udp_payload_size
(an int), extended_rcode
(an int), version
(an int), do
(an int), and z
(an int).
The rdata
dict has different names for each response type. There is a complete list of the types defined in the API. For names that end in
"-obsolete" or "-unknown", the bindata is the entire RDATA field. For example, the
rdata
for an A record has a name ipv4_address
(a bindata); the
rdata
for an SRV record has the names priority
(an int),
weight
(an int), port
(an int), and target
(a bindata).
Each rdata
dict also has a rdata_raw
field (a bindata). This is useful
for types not defined in this version of the API. It also might be of value if a later version of
the API allows for additional parsers. Thus, doing a query for types not known by the API still will
return a result: an rdata
with just a rdata_raw
.
It is expected that later extensions to the API will give some DNS types different names. It is also possible that later extensions will change the names for some of the DNS types listed above.
For example, a response to a getdns_address()
call for www.example.com would
look something like this:
{ # This is the response object "replies_full": [ <bindata of the first response>, <bindata of the second response> ], "just_address_answers": [ { "address_type": <bindata of "IPv4">, "address_data": <bindata of 0x0a0b0c01>, }, { "address_type": <bindata of "IPv6">, "address_data": <bindata of 0x33445566334455663344556633445566> } ], "canonical_name": <bindata of "www.example.com">, "answer_type": GETDNS_NAMETYPE_DNS, "intermediate_aliases": [], "replies_tree": [ { # This is the first reply "header": { "id": 23456, "qr": 1, "opcode": 0, ... }, "question": { "qname": <bindata of "www.example.com">, "qtype": 1, "qclass": 1 }, "answer": [ { "name": <bindata of "www.example.com">, "type": 1, "class": 1, "ttl": 33000, "rdata": { "ipv4_address": <bindata of 0x0a0b0c01> "rdata_raw": <bindata of 0x0a0b0c01> } } ], "authority": [ { "name": <bindata of "ns1.example.com">, "type": 1, "class": 1, "ttl": 600, "rdata": { "ipv4_address": <bindata of 0x65439876> "rdata_raw": <bindata of 0x65439876> } } ] "additional": [], "canonical_name": <bindata of "www.example.com">, "answer_type": GETDNS_NAMETYPE_DNS }, { # This is the second reply "header": { "id": 47809, "qr": 1, "opcode": 0, ... }, "question": { "qname": <bindata of "www.example.com">, "qtype": 28, "qclass": 1 }, "answer": [ { "name": <bindata of "www.example.com">, "type": 28, "class": 1, "ttl": 1000, "rdata": { "ipv6_address": <bindata of 0x33445566334455663344556633445566> "rdata_raw": <bindata of 0x33445566334455663344556633445566> } } ], "authority": [ # Same as for other record... ] "additional": [], }, ] }
In DNS responses, domain names are treated special. RFC 1035 describes a form of name compression
that requires that the entire record be available for analysis. The API deals with this by
converting compressed names into full names when returning names in the replies_tree
.
This conversion happens for qname
in question
; name
in the
answer
, authority
, and additional
; and in domain names in the
data in names under rdata
where the response type is AFSDB, CNAME, MX, NS, PTR, RP, RT, and SOA.
Names in DNS fields are stored in a fashion very different from the normal presentation format
normally used in applications. The DNS format is described in the first paragraph in Section 3.1 of
RFC 1035; the presentation format here is a null-terminated string with interior dots. These helper
functions only work with names in the DNS format that are not compressed. They are useful for
converting domain names in the replies_tree
to and from the FQDN presentation
format.
getdns_convert_dns_name_to_fqdn()
converts a domain name in DNS format to the
presentation format. For example, the hex sequence 03 77 77 77 07 65 78 61 6d 70 6c 65 03 63
6f 6d 00
would be converted to "www.example.com".
getdns_convert_fqdn_to_dns_name()
does the reverse: it converts a null-terminated
string in FQDN format to bytes in DNS format.
The returned values are allocated with the default system allocator, namely malloc
.
The caller is responsible of disposing these allocations with free
.
The return codes for all the functions are:
GETDNS_RETURN_GOOD
Good
GETDNS_RETURN_GENERIC_ERROR
Generic error
GETDNS_RETURN_BAD_DOMAIN_NAME
Badly-formed domain name in first argument
GETDNS_RETURN_BAD_CONTEXT
The context has internal deficiencies
GETDNS_RETURN_CONTEXT_UPDATE_FAIL
Did not update the context
GETDNS_RETURN_UNKNOWN_TRANSACTION
An attempt was made to cancel a callback with a transaction_id that is not recognized
GETDNS_RETURN_NO_SUCH_LIST_ITEM
A helper function for lists had an index argument that was too high.
GETDNS_RETURN_NO_SUCH_DICT_NAME
A helper function for dicts had a name argument that for a name that is not in the dict.
GETDNS_RETURN_WRONG_TYPE_REQUESTED
A helper function was supposed to return a certain type for an item, but the wrong type was given.
GETDNS_RETURN_NO_SUCH_EXTENSION
A name in the extensions dict is not a valid extension.
GETDNS_RETURN_EXTENSION_MISFORMAT
One or more of the extensions have a bad format.
GETDNS_RETURN_DNSSEC_WITH_STUB_DISALLOWED
A query was made with a context that is using stub resolution and a DNSSEC extension specified.
GETDNS_RETURN_MEMORY_ERROR
Unable to allocate the memory required.
GETDNS_RETURN_INVALID_PARAMETER
A required parameter had an invalid value.
The names in the rdata
dicts in replies are:
A (1)
ipv4_address
(a bindata)
NS (2)
nsdname
(a bindata)
MD (3)
madname
(a bindata)
MF (4)
madname
(a bindata)
CNAME (5)
cname
(a bindata)
SOA (6)
mname
(a bindata), rname
(a bindata),
serial
(an int), refresh
(an int), refresh
(an int),
retry
(an int), and expire
(an int)
MB (7)
madname
(a bindata)
MG (8)
mgmname
(a bindata)
MR (9)
newname
(a bindata)
NULL (10)
anything
(a bindata)
WKS (11)
address
(a bindata), protocol
(an int),
and bitmap
(a bindata)
PTR (12)
ptrdname
(a bindata)
HINFO (13)
cpu
(a bindata) and os
(a bindata)
MINFO (14)
rmailbx
(a bindata) and emailbx
(a bindata)
MX (15)
preference
(an int) and exchange
(a bindata)
TXT (16)
txt_strings
(a list) which contains zero or more bindata elements
that are text strings
RP (17)
mbox_dname
(a bindata) and txt_dname
(a bindata)
AFSDB (18)
subtype
(an int) and hostname
(a bindata)
X25 (19)
psdn_address
(a bindata)
ISDN (20)
isdn_address
(a bindata) and sa
(a bindata)
RT (21)
preference
(an int) and intermediate_host
(a bindata)
NSAP (22)
nsap
(a bindata)
SIG (24)
sig_obsolete
(a bindata)
KEY (25)
key_obsolete
(a bindata)
PX (26)
preference
(an int), map822
(a bindata), and mapx400
(a bindata)
GPOS (27)
longitude
(a bindata), latitude
(a bindata), and altitude
(a bindata)
AAAA (28)
ipv6_address
(a bindata)
LOC (29)
loc_obsolete
(a bindata)
NXT (30)
nxt_obsolete
(a bindata)
EID (31)
eid_unknown
(a bindata)
NIMLOC (32)
nimloc_unknown
(a bindata)
SRV (33)
priority
(an int), weight
(an int),
port
(an int), and target
(a bindata)
ATMA (34)
format
(an int) and address
(a bindata)
NAPTR (35)
order
(an int), preference
(an int), flags
(a bindata), service
(a bindata), regexp
(a bindata), and
replacement
(a bindata).
KX (36)
preference
(an int) and exchanger
(a bindata)
CERT (37)
type
(an int), key_tag
(an int), algorithm
(an int),
and certificate_or_crl
(a bindata)
A6 (38)
a6_obsolete
(a bindata)
DNAME (39)
target
(a bindata)
SINK (40)
sink_unknown
(a bindata)
OPT (41)
options
(a list). Each element of the options
list is a
dict with two names: option_code
(an int) and option_data
(a bindata).
APL (42)
apitems
(a list).
Each element of the apitems
list is a dict with four names:
address_family
(an int), prefix
(an int),
n
(an int), and afdpart
(a bindata)
DS (43)
key_tag
(an int), algorithm
(an int), digest_type
(an int),
and digest
(a bindata)
SSHFP (44)
algorithm
(an int), fp_type
(an int),
and fingerprint
(a bindata)
IPSECKEY (45)
algorithm
(an int), gateway_type
(an int), precedence
(an int),
gateway
, and public_key
(a bindata)
RRSIG (46)
type_covered
(an int), algorithm
(an int),
labels
(an int), original_ttl
(an int), signature_expiration
(an int), signature_inception
(an int), key_tag
(an int),
signers_name
(a bindata), and signature
(a bindata)
NSEC (47)
next_domain_name
(a bindata) and type_bit_maps
(a bindata)
DNSKEY (48)
flags
(an int), protocol
(an int), algorithm
(an int),
and public_key
(a bindata)
DHCID (49)
dhcid_opaque
(a bindata)
NSEC3 (50)
hash_algorithm
(an int), flags
(an int),
iterations
(an int), salt
(a bindata),
next_hashed_owner_name
(a bindata), and
type_bit_maps
(a bindata)
NSEC3PARAM (51)
hash_algorithm
(an int), flags
(an int),
iterations
(an int), and
salt
(a bindata)
TLSA (52)
certificate_usage
(an int), selector
(an int),
matching_type
(an int), and certificate_association_data
(a
bindata).
HIP (55)
pk_algorithm
(an int),
hit
(a bindata), public_key
(a bindata), and rendezvous_servers
(a list) with each element a bindata with the dname of the rendezvous_server.
NINFO (56)
ninfo_unknown
(a bindata)
RKEY (57)
rkey_unknown
(a bindata)
TALINK (58)
talink_unknown
(a bindata)
CDS (59)
key_tag
(an int), algorithm
(an int), digest_type
(an int),
and digest
(a bindata)
CDNSKEY (60)
flags
(an int), protocol
(an int), algorithm
(an int),
and public_key
(a bindata)
OPENPGPKEY (61)
openpgpkey_unknown
(a bindata)
SPF (99)
text
(a bindata)
UINFO (100)
uinfo_unknown
(a bindata)
UID (101)
uid_unknown
(a bindata)
GID (102)
gid_unknown
(a bindata)
UNSPEC (103)
unspec_unknown
(a bindata)
NID (104)
preference
(an int) and
node_id
(a bindata)
L32 (105)
preference
(an int) and locator32
(a bindata)
L64 (106)
preference
(an int) and locator64
(a bindata)
LP (107)
preference
(an int) and fqdn
(a bindata)
EUI48 (108)
eui48_address
(a bindata)
EUI64 (109)
eui64_address
(a bindata)
TKEY (249)
algorithm
(a bindata), inception
(an int),
expiration
(an int), mode
(an int), error
(an int),
key_data
(a bindata), and other_data
(a bindata)
TSIG (250)
algorithm
(a bindata), time_signed
(a bindata),
fudge
(an int), mac
(a bindata), original_id
(an int),
error
(an int), and other_data
(a bindata)
MAILB (253)
mailb-unknown
(a bindata)
MAILA (254)
maila-unknown
(a bindata)
URI (256)
priority
(an int), weight
(an int),
and target
(a bindata)
CAA (257)
flags
(an int), tag
(a bindata), and value
(a bindata)
TA (32768)
ta_unknown
(a bindata)
DLV (32769)
Identical to DS (43)
This section gives examples of code that calls the API to do many common tasks. The purpose of the code here is to give application developers a quick hands-on demo of using the API.
Note that the examples here all use getdns_libevent.h as the include that will call in the API
code as well as calling in libevent as the event library. They also use
getdns_context_set_libevent_base()
as the name of the function to set the event base in
the DNS context. If you are using a different event library, you will of course use a different
#include
at the beginning of your code, and a different name for the event base
function.
This is an example of a common call to getdns_address()
.
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <getdns_libevent.h>
#define UNUSED_PARAM(x) ((void)(x))
/* Set up the callback function, which will also do the processing of the results */
void this_callbackfn(getdns_context *this_context,
getdns_callback_type_t this_callback_type,
getdns_dict *this_response,
void *this_userarg,
getdns_transaction_t this_transaction_id)
{
UNUSED_PARAM(this_userarg); /* Not looking at the userarg for this example */
UNUSED_PARAM(this_context); /* Not looking at the context for this example */
getdns_return_t this_ret; /* Holder for all function returns */
if (this_callback_type == GETDNS_CALLBACK_COMPLETE) /* This is a callback with data */
{
/* Be sure the search returned something */
uint32_t this_error;
this_ret = getdns_dict_get_int(this_response, "status", &this_error); // Ignore any error
if (this_error != GETDNS_RESPSTATUS_GOOD) // If the search didn't return "good"
{
fprintf(stderr, "The search had no results, and a return value of %d. Exiting.\n", this_error);
getdns_dict_destroy(this_response);
return;
}
getdns_list * just_the_addresses_ptr;
this_ret = getdns_dict_get_list(this_response, "just_address_answers", &just_the_addresses_ptr);
if (this_ret != GETDNS_RETURN_GOOD) // This check is really not needed, but prevents a compiler error under "pedantic"
{
fprintf(stderr, "Trying to get the answers failed: %d\n", this_ret);
getdns_dict_destroy(this_response);
return;
}
size_t num_addresses;
this_ret = getdns_list_get_length(just_the_addresses_ptr, &num_addresses); // Ignore any error
/* Go through each record */
for ( size_t rec_count = 0; rec_count < num_addresses; ++rec_count )
{
getdns_dict * this_address;
this_ret = getdns_list_get_dict(just_the_addresses_ptr, rec_count, &this_address); // Ignore any error
/* Just print the address */
getdns_bindata * this_address_data;
this_ret = getdns_dict_get_bindata(this_address, "address_data", &this_address_data); // Ignore any error
char *this_address_str = getdns_display_ip_address(this_address_data);
printf("The address is %s\n", this_address_str);
free(this_address_str);
}
}
else if (this_callback_type == GETDNS_CALLBACK_CANCEL)
fprintf(stderr, "The callback with ID %"PRIu64" was cancelled. Exiting.\n", this_transaction_id);
else
fprintf(stderr, "The callback got a callback_type of %d. Exiting.\n", this_callback_type);
getdns_dict_destroy(this_response);
}
int main()
{
/* Create the DNS context for this call */
getdns_context *this_context = NULL;
getdns_return_t context_create_return = getdns_context_create(&this_context, 1);
if (context_create_return != GETDNS_RETURN_GOOD)
{
fprintf(stderr, "Trying to create the context failed: %d", context_create_return);
return(GETDNS_RETURN_GENERIC_ERROR);
}
/* Create an event base and put it in the context using the unknown function name */
struct event_base *this_event_base;
this_event_base = event_base_new();
if (this_event_base == NULL)
{
fprintf(stderr, "Trying to create the event base failed.\n");
getdns_context_destroy(this_context);
return(GETDNS_RETURN_GENERIC_ERROR);
}
(void)getdns_extension_set_libevent_base(this_context, this_event_base);
/* Set up the getdns call */
const char * this_name = "www.example.com";
char* this_userarg = "somestring"; // Could add things here to help identify this call
getdns_transaction_t this_transaction_id = 0;
/* Make the call */
getdns_return_t dns_request_return = getdns_address(this_context, this_name,
NULL, this_userarg, &this_transaction_id, this_callbackfn);
if (dns_request_return == GETDNS_RETURN_BAD_DOMAIN_NAME)
{
fprintf(stderr, "A bad domain name was used: %s. Exiting.\n", this_name);
event_base_free(this_event_base);
getdns_context_destroy(this_context);
return(GETDNS_RETURN_GENERIC_ERROR);
}
else
{
/* Call the event loop */
int dispatch_return = event_base_dispatch(this_event_base);
UNUSED_PARAM(dispatch_return);
// TODO: check the return value above
}
/* Clean up */
event_base_free(this_event_base);
getdns_context_destroy(this_context);
/* Assuming we get here, leave gracefully */
exit(EXIT_SUCCESS);
}
This example is similar to the previous one, except that it retrieves more information than just the addresses, so it traverses the replies_tree. In this case, it gets both the addresses and their TTLs.
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <getdns_libevent.h>
#define UNUSED_PARAM(x) ((void)(x))
/* Set up the callback function, which will also do the processing of the results */
void this_callbackfn(getdns_context *this_context,
getdns_callback_type_t this_callback_type,
getdns_dict *this_response,
void *this_userarg,
getdns_transaction_t this_transaction_id)
{
UNUSED_PARAM(this_userarg); /* Not looking at the userarg for this example */
UNUSED_PARAM(this_context); /* Not looking at the context for this example */
getdns_return_t this_ret; /* Holder for all function returns */
if (this_callback_type == GETDNS_CALLBACK_COMPLETE) /* This is a callback with data */
{
/* Be sure the search returned something */
uint32_t this_error;
this_ret = getdns_dict_get_int(this_response, "status", &this_error); // Ignore any error
if (this_error != GETDNS_RESPSTATUS_GOOD) // If the search didn't return "good"
{
fprintf(stderr, "The search had no results, and a return value of %d. Exiting.\n", this_error);
getdns_dict_destroy(this_response);
return;
}
/* Find all the answers returned */
getdns_list * these_answers;
this_ret = getdns_dict_get_list(this_response, "replies_tree", &these_answers);
if (this_ret == GETDNS_RETURN_NO_SUCH_DICT_NAME)
{
fprintf(stderr, "Weird: the response had no error, but also no replies_tree. Exiting.\n");
getdns_dict_destroy(this_response);
return;
}
size_t num_answers;
this_ret = getdns_list_get_length(these_answers, &num_answers);
/* Go through each answer */
for ( size_t rec_count = 0; rec_count < num_answers; ++rec_count )
{
getdns_dict * this_record;
this_ret = getdns_list_get_dict(these_answers, rec_count, &this_record); // Ignore any error
/* Get the answer section */
getdns_list * this_answer;
this_ret = getdns_dict_get_list(this_record, "answer", &this_answer); // Ignore any error
/* Get each RR in the answer section */
size_t num_rrs;
this_ret = getdns_list_get_length(this_answer, &num_rrs);
for ( size_t rr_count = 0; rr_count < num_rrs; ++rr_count )
{
getdns_dict *this_rr = NULL;
this_ret = getdns_list_get_dict(this_answer, rr_count, &this_rr); // Ignore any error
/* Get the RDATA */
getdns_dict * this_rdata = NULL;
this_ret = getdns_dict_get_dict(this_rr, "rdata", &this_rdata); // Ignore any error
/* Get the RDATA type */
uint32_t this_type;
this_ret = getdns_dict_get_int(this_rr, "type", &this_type); // Ignore any error
/* If it is type A or AAAA, print the value */
if (this_type == GETDNS_RRTYPE_A)
{
getdns_bindata * this_a_record = NULL;
this_ret = getdns_dict_get_bindata(this_rdata, "ipv4_address", &this_a_record);
if (this_ret == GETDNS_RETURN_NO_SUCH_DICT_NAME)
{
fprintf(stderr, "Weird: the A record at %d in record at %d had no address. Exiting.\n",
(int) rr_count, (int) rec_count);
getdns_dict_destroy(this_response);
return;
}
char *this_address_str = getdns_display_ip_address(this_a_record);
printf("The IPv4 address is %s\n", this_address_str);
free(this_address_str);
}
else if (this_type == GETDNS_RRTYPE_AAAA)
{
getdns_bindata * this_aaaa_record = NULL;
this_ret = getdns_dict_get_bindata(this_rdata, "ipv6_address", &this_aaaa_record);
if (this_ret == GETDNS_RETURN_NO_SUCH_DICT_NAME)
{
fprintf(stderr, "Weird: the AAAA record at %d in record at %d had no address. Exiting.\n",
(int) rr_count, (int) rec_count);
getdns_dict_destroy(this_response);
return;
}
char *this_address_str = getdns_display_ip_address(this_aaaa_record);
printf("The IPv6 address is %s\n", this_address_str);
free(this_address_str);
}
}
}
}
else if (this_callback_type == GETDNS_CALLBACK_CANCEL)
fprintf(stderr, "The callback with ID %"PRIu64" was cancelled. Exiting.\n", this_transaction_id);
else
fprintf(stderr, "The callback got a callback_type of %d. Exiting.\n", this_callback_type);
getdns_dict_destroy(this_response);
}
int main()
{
/* Create the DNS context for this call */
getdns_context *this_context = NULL;
getdns_return_t context_create_return = getdns_context_create(&this_context, 1);
if (context_create_return != GETDNS_RETURN_GOOD)
{
fprintf(stderr, "Trying to create the context failed: %d\n", context_create_return);
return(GETDNS_RETURN_GENERIC_ERROR);
}
/* Create an event base and put it in the context using the unknown function name */
struct event_base *this_event_base;
this_event_base = event_base_new();
if (this_event_base == NULL)
{
fprintf(stderr, "Trying to create the event base failed.\n");
getdns_context_destroy(this_context);
return(GETDNS_RETURN_GENERIC_ERROR);
}
(void)getdns_extension_set_libevent_base(this_context, this_event_base);
/* Set up the getdns call */
const char * this_name = "www.example.com";
char* this_userarg = "somestring"; // Could add things here to help identify this call
getdns_transaction_t this_transaction_id = 0;
/* Make the call */
getdns_return_t dns_request_return = getdns_address(this_context, this_name,
NULL, this_userarg, &this_transaction_id, this_callbackfn);
if (dns_request_return == GETDNS_RETURN_BAD_DOMAIN_NAME)
{
fprintf(stderr, "A bad domain name was used: %s. Exiting.\n", this_name);
event_base_free(this_event_base);
getdns_context_destroy(this_context);
return(GETDNS_RETURN_GENERIC_ERROR);
}
else
{
/* Call the event loop */
int dispatch_return = event_base_dispatch(this_event_base);
UNUSED_PARAM(dispatch_return);
// TODO: check the return value above
}
/* Clean up */
event_base_free(this_event_base);
getdns_context_destroy(this_context);
/* Assuming we get here, leave gracefully */
exit(EXIT_SUCCESS);
}
This example shows how to check for secure DNSSEC results using the
dnssec_return_status
extension. In the innermost loop of the
callback function, add a check for the DNSSEC status. It shows how to add two
extensions to the extensions
argument of the call.
getdns_dict * this_extensions = getdns_dict_create(); this_ret = getdns_dict_set_int(this_extensions, "return_both_v4_and_v6", GETDNS_EXTENSION_TRUE); this_ret = getdns_dict_set_int(this_extensions, "dnssec_return_status", GETDNS_EXTENSION_TRUE); . . . if (this_type == GETDNS_RRTYPE_A) { uint32_t this_dnssec_status; this_ret = getdns_dict_get_int(this_answer, "dnssec_status", &this_dnssec_status); if (this_dnssec_status != GETDNS_DNSSEC_SECURE) { // Log the DNSSEC status somewhere } else { // Deal with the record however you were going to } } . . .
You can put the DNSSEC status check outside the check for the particular type of record you care about, but you will then get log messages for bad status on records you might not care about as well.
getdns_general_sync()
This example is the same as the earlier examples, but uses getdns_general_sync()
and thus does not use the async code. Note that the processing of the answers is essentially the same
as it is for the synchronous example, it is just done in main()
.
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <getdns_core_only.h>
int main()
{
getdns_return_t this_ret; /* Holder for all function returns */
/* Create the DNS context for this call */
getdns_context *this_context = NULL;
getdns_return_t context_create_return = getdns_context_create(&this_context, 1);
if (context_create_return != GETDNS_RETURN_GOOD)
{
fprintf(stderr, "Trying to create the context failed: %d\n", context_create_return);
return(GETDNS_RETURN_GENERIC_ERROR);
}
/* Set up the getdns_sync_request call */
const char * this_name = "www.example.com";
uint8_t this_request_type = GETDNS_RRTYPE_A;
/* Get the A and AAAA records */
getdns_dict * this_extensions = getdns_dict_create();
this_ret = getdns_dict_set_int(this_extensions, "return_both_v4_and_v6", GETDNS_EXTENSION_TRUE);
if (this_ret != GETDNS_RETURN_GOOD)
{
fprintf(stderr, "Trying to set an extension do both IPv4 and IPv6 failed: %d\n", this_ret);
getdns_dict_destroy(this_extensions);
getdns_context_destroy(this_context);
return(GETDNS_RETURN_GENERIC_ERROR);
}
getdns_dict * this_response = NULL;
/* Make the call */
getdns_return_t dns_request_return = getdns_general_sync(this_context, this_name, this_request_type,
this_extensions, &this_response);
if (dns_request_return == GETDNS_RETURN_BAD_DOMAIN_NAME)
{
fprintf(stderr, "A bad domain name was used: %s. Exiting.\n", this_name);
getdns_dict_destroy(this_response);
getdns_dict_destroy(this_extensions);
getdns_context_destroy(this_context);
return(GETDNS_RETURN_GENERIC_ERROR);
}
else
{
/* Be sure the search returned something */
uint32_t this_error;
this_ret = getdns_dict_get_int(this_response, "status", &this_error); // Ignore any error
if (this_error != GETDNS_RESPSTATUS_GOOD) // If the search didn't return "good"
{
fprintf(stderr, "The search had no results, and a return value of %d. Exiting.\n", this_error);
getdns_dict_destroy(this_response);
getdns_dict_destroy(this_extensions);
getdns_context_destroy(this_context);
return(GETDNS_RETURN_GENERIC_ERROR);
}
getdns_list * just_the_addresses_ptr;
this_ret = getdns_dict_get_list(this_response, "just_address_answers", &just_the_addresses_ptr); // Ignore any error
size_t num_addresses;
this_ret = getdns_list_get_length(just_the_addresses_ptr, &num_addresses); // Ignore any error
/* Go through each record */
for ( size_t rec_count = 0; rec_count < num_addresses; ++rec_count )
{
getdns_dict * this_address;
this_ret = getdns_list_get_dict(just_the_addresses_ptr, rec_count, &this_address); // Ignore any error
/* Just print the address */
getdns_bindata * this_address_data;
this_ret = getdns_dict_get_bindata(this_address, "address_data", &this_address_data); // Ignore any error
char *this_address_str = getdns_display_ip_address(this_address_data);
printf("The address is %s\n", this_address_str);
free(this_address_str);
}
}
/* Clean up */
getdns_dict_destroy(this_response);
getdns_dict_destroy(this_extensions);
getdns_context_destroy(this_context);
/* Assuming we get here, leave gracefully */
exit(EXIT_SUCCESS);
}
getdns_hostname()
This example shows how to use getdns_hostname()
to get names from the DNS reverse tree.
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <getdns_libevent.h>
#define UNUSED_PARAM(x) ((void)(x))
/* Set up the callback function, which will also do the processing of the results */
void this_callbackfn(getdns_context *this_context,
getdns_callback_type_t this_callback_type,
getdns_dict *this_response,
void *this_userarg,
getdns_transaction_t this_transaction_id)
{
getdns_return_t this_ret; /* Holder for all function returns */
UNUSED_PARAM(this_userarg); /* Not looking at the userarg for this example */
UNUSED_PARAM(this_context); /* Not looking at the context for this example */
UNUSED_PARAM(this_ret); /* Set, but not read */
if (this_callback_type == GETDNS_CALLBACK_COMPLETE) /* This is a callback with data */
{
/* Be sure the search returned something */
uint32_t this_error;
this_ret = getdns_dict_get_int(this_response, "status", &this_error); // Ignore any error
if (this_error != GETDNS_RESPSTATUS_GOOD) // If the search didn't return "good"
{
fprintf(stderr, "The search had no results, and a return value of %d. Exiting.\n", this_error);
getdns_dict_destroy(this_response);
return;
}
getdns_list *replies_tree;
this_ret = getdns_dict_get_list(this_response, "replies_tree", &replies_tree); // Ignore any error
size_t num_replies;
this_ret = getdns_list_get_length(replies_tree, &num_replies); // Ignore any error
/* Go through each reply */
for ( size_t reply_count = 0; reply_count < num_replies; ++reply_count)
{
getdns_dict * this_reply;
this_ret = getdns_list_get_dict(replies_tree, reply_count, &this_reply); // Ignore any error
/* Just print the address */
getdns_list* reply_answers;
this_ret = getdns_dict_get_list(this_reply, "answer", &reply_answers); // Ignore any error
size_t num_answers;
this_ret = getdns_list_get_length(reply_answers, &num_answers); // Ignore any error
/* Go through each answer */
for ( size_t answer_count = 0; answer_count < num_answers; ++answer_count)
{
getdns_dict * this_rr;
this_ret = getdns_list_get_dict(reply_answers, answer_count, &this_rr);
/* Get the RDATA type */
uint32_t this_type;
this_ret = getdns_dict_get_int(this_rr, "type", &this_type); // Ignore any error
if (this_type == GETDNS_RRTYPE_PTR)
{
getdns_dict *this_rdata;
this_ret = getdns_dict_get_dict(this_rr, "rdata", &this_rdata); // Ignore any error
getdns_bindata * this_dname;
this_ret = getdns_dict_get_bindata(this_rdata, "rdata_raw", &this_dname);
char *this_dname_str;
this_ret = getdns_convert_dns_name_to_fqdn(this_dname, &this_dname_str); // Ignore any error
printf("The dname is %s\n", this_dname_str);
free(this_dname_str);
}
}
}
}
else if (this_callback_type == GETDNS_CALLBACK_CANCEL)
fprintf(stderr, "The callback with ID %"PRIu64" was cancelled. Exiting.", this_transaction_id);
else
fprintf(stderr, "The callback got a callback_type of %d. Exiting.", this_callback_type);
getdns_dict_destroy(this_response);
}
int main()
{
/* Create the DNS context for this call */
getdns_context *this_context = NULL;
getdns_return_t context_create_return = getdns_context_create(&this_context, 1);
if (context_create_return != GETDNS_RETURN_GOOD)
{
fprintf(stderr, "Trying to create the context failed: %d", context_create_return);
return(GETDNS_RETURN_GENERIC_ERROR);
}
/* Create an event base and put it in the context using the unknown function name */
struct event_base *this_event_base;
this_event_base = event_base_new();
if (this_event_base == NULL)
{
fprintf(stderr, "Trying to create the event base failed.");
getdns_context_destroy(this_context);
return(GETDNS_RETURN_GENERIC_ERROR);
}
(void)getdns_extension_set_libevent_base(this_context, this_event_base);
/* Set up the getdns call */
getdns_dict * this_addr_to_look_up = getdns_dict_create();
// TODO: check the return value above
getdns_bindata this_type = { 4, (void *)"IPv4" };
getdns_return_t this_ret = getdns_dict_set_bindata(this_addr_to_look_up, "address_type", &this_type);
UNUSED_PARAM(this_ret);
getdns_bindata this_ipv4_addr = { 4, (void *)"\x08\x08\x08\x08" };
this_ret = getdns_dict_set_bindata(this_addr_to_look_up, "address_data", &this_ipv4_addr);
char* this_userarg = "somestring"; // Could add things here to help identify this call
getdns_transaction_t this_transaction_id = 0;
/* Make the call */
getdns_return_t dns_request_return = getdns_hostname(this_context, this_addr_to_look_up,
NULL, this_userarg, &this_transaction_id, this_callbackfn);
if (dns_request_return == GETDNS_RETURN_BAD_DOMAIN_NAME)
{
char *ip_address_str = getdns_display_ip_address(&this_ipv4_addr);
fprintf(stderr, "A bad IP address was used: %s. Exiting.\n", ip_address_str);
free(ip_address_str);
getdns_dict_destroy(this_addr_to_look_up);
event_base_free(this_event_base);
getdns_context_destroy(this_context);
return(GETDNS_RETURN_GENERIC_ERROR);
}
else
{
/* Call the event loop */
int dispatch_return = event_base_dispatch(this_event_base);
UNUSED_PARAM(dispatch_return);
// TODO: check the return value above
}
/* Clean up */
getdns_dict_destroy(this_addr_to_look_up);
event_base_free(this_event_base);
getdns_context_destroy(this_context);
/* Assuming we get here, leave gracefully */
exit(EXIT_SUCCESS);
}
The following two functions convert individual labels of IDNs between their Unicode encoding and their ASCII encoding. They follow the rules for IDNA 2008 described in RFC 5890-5892.
If an application wants the API do perform DNSSEC validation without using the extensions, it
can use the getdns_validate_dnssec()
helper function.
The record_to_validate
is the resource record being validated together with the associated signatures.
The API will use the resource records in bundle_of_support_records
to construct the validation chain and the DNSKEY or DS records in trust_anchor_records
as trust anchors.
The function returns one of GETDNS_DNSSEC_SECURE
, GETDNS_DNSSEC_BOGUS
, GETDNS_DNSSEC_INDETERMINATE
, or GETDNS_DNSSEC_INSECURE
.
The default list of trust anchor records that is used by the library to validate DNSSEC can
be retrieved by using the getdns_root_trust_anchor
helper function.
When there are no default trust anchors NULL
is returned.
Upon successful return, the variable of type time_t
, referenced by
utc_date_of_anchor
is set to the number of seconds since epoch
the trust anchors were obtained.
There are two functions that help process data:
This returns a string that is the nicely-formatted version of the dict and all of the named elements in it.
This returns a string that is the nicely-formatted version of the IPv4 or IPv6 address in it. The API determines they type of address by the length given in the bindata.
All memory locations returned by these helper functions are allocated by the default system allocator, namely malloc
.
The caller is responsible of disposing these allocations with free
.
Many calls in the DNS API require a DNS context. A DNS context contains the information that the API needs in order to process DNS calls, such as the locations of upstream DNS servers, DNSSEC trust anchors, and so on. The internal structure of the DNS context is opaque, and might be different on each OS. When a context is passed to any function, it must be an allocated context; the context must not be NULL.
A typical application using this API doesn't need to know anything about contexts. Basically, the application creates a default context, uses it in the functions that require a context, and then deallocates it when done. Context manipulation is available for more DNS-aware programs, but is unlikely to be of interest to applications that just want the results of lookups for A, AAAA, SRV, and PTR records.
It is expected that contexts in implementations of the API will not necessarily be thread-safe, but they will not be thread-hostile. A context should not be used by multiple threads: create a new context for use on a different thread. It is just fine for an application to have many contexts, and some DNS-heavy applications will certainly want to have many even if the application uses a single thread.
See above for the method for creating and destroying
contexts. When the context is used in the API for the first time and set_from_os
is
1
, the API starts replacing some of the values with values from the OS, such as
those that would be found in res_query(3), /etc/resolv.conf, and so on, then proceeds with the new
function. Some advanced users will not want the API to change the values to the OS's defaults; if
set_from_os
is 0
, the API will not do any updates to the initial
values based on changes in the OS. For example, this might be useful if the API is acting
as a stub resolver that is using a specific upstream recursive resolver chosen by the
application, not the one that might come back from DHCP.
The context returned by getdns_context_create()
is updated by the API by default,
such as when changes are made to /etc/resolv.conf. When there is a change, the callback function
that is set in getdns_context_set_context_update_callback()
(described below) is
called.
Many of the defaults for a context come from the operating system under which the API is running.
In specific, it is important that the implementation should try to replicate as best as possible the
logic of a local getaddrinfo()
when creating a new context. This includes making
lookups in WINS for NetBIOS, mDNS lookups, nis names, and any other name lookup that
getaddrinfo()
normally does automatically. The API should look at nsswitch, the Windows
resolver, and so on.
In the function definitions below, the choice listed in bold is the one used for the API default context.
Setting specific values in a context are done with value-specific
functions shown here. The setting functions all return either GETDNS_RETURN_GOOD
for
success or GETDNS_RETURN_CONTEXT_UPDATE_FAIL
for a failure to update the context.
An application can be notified when the context is changed.
The value is a pointer to the callback function that will be called when any context is changed. Such changes might be from automatic changes from the API (such as changes to /etc/resolv.conf), or might be from any of the API functions in this section being called. The second argument to the callback function specifies which of the context changed; the context codes are listed later in this document.
Calling getdns_context_set_context_update_callback with a second argument of NULL prevents updates to the context from causing callbacks.
Specifies whether DNS queries are performed with nonrecurive lookups or
as a stub resolver. The value is GETDNS_RESOLUTION_RECURSING
or
GETDNS_RESOLUTION_STUB
.
All implementations of this API can act as recursive resolvers, and that must be the
default mode of the default context.
Some implementations of this API are expected to also be able to act as stub resolvers.
If an
implementation of this API is only able to act as a recursive resolver, a call to
getdns_context_set_resolution_type(somecontext, GETDNS_RESOLUTION_STUB)
will
return GETDNS_RETURN_CONTEXT_UPDATE_FAIL
.
The namespaces
array contains an ordered list
of namespaces that will be queried.
Important: this context setting is ignored for the getdns_general
and
getdns_general_sync
functions; it is used for the other funtions.
The values
are GETDNS_NAMESPACE_DNS
,
GETDNS_NAMESPACE_LOCALNAMES
,
GETDNS_NAMESPACE_NETBIOS
,
GETDNS_NAMESPACE_MDNS
, and
GETDNS_NAMESPACE_NIS
. When a normal lookup is done,
the API does the lookups in the order given and stops when it gets the
first result; a different method with the same result would be to run
the queries in parallel and return when it gets the first result.
Because lookups might be done over different mechanisms because of the
different namespaces, there can be information leakage that is similar
to that seen with getaddrinfo()
. The
default is determined by the OS.
Specifies what transport is used for DNS lookups.
The value is
GETDNS_TRANSPORT_UDP_FIRST_AND_FALL_BACK_TO_TCP
,
GETDNS_TRANSPORT_UDP_ONLY
,
GETDNS_TRANSPORT_TCP_ONLY
, or
GETDNS_TRANSPORT_TCP_ONLY_KEEP_CONNECTIONS_OPEN
.
Specifies limit the number of outstanding DNS queries. The API will block itself from sending more queries if it is about to exceed this value, and instead keep those queries in an internal queue. The a value of 0 indicates that the number of outstanding DNS queries is unlimited.
Specifies number of milliseconds the API will wait for request to return. The default is not specified.
Specifies whether or not DNS queries follow redirects.
The value is GETDNS_REDIRECTS_FOLLOW
for normal
following of redirects though CNAME and DNAME; or
GETDNS_REDIRECTS_DO_NOT_FOLLOW
to cause any lookups that would have gone
through CNAME and DNAME to return the CNAME or DNAME, not the eventual target.
The list contains dicts that are addresses to be used for looking up top-level
domains; the default is the list of "normal" IANA root servers. Each dict in the list
contains at least two names: address_type
(whose value is a bindata; it is currently
either "IPv4" or "IPv6") and address_data
(whose value is a bindata).
Specifies whether to append a suffix to the query string
before the API starts resolving a name.
The value is
GETDNS_APPEND_NAME_ALWAYS
,
GETDNS_APPEND_NAME_ONLY_TO_SINGLE_LABEL_AFTER_FAILURE
,
GETDNS_APPEND_NAME_ONLY_TO_MULTIPLE_LABEL_NAME_AFTER_FAILURE
,
or GETDNS_APPEND_NAME_NEVER
. This controls
whether or not to append the suffix given by getdns_context_set_suffix
The value is a list of bindatas that are strings that are
to be appended based on getdns_context_set_append_name
; the default is an empty list. The values here follow the rules in section 2.1 of RFC 4343
to allow non-ASCII octets and special characters in labels.
These context settings affect queries that have extensions that specify the use of DNSSEC.
Applications that need to specify the DNSSEC trust anchors can use:
The value is a list of bindatas that are the DNSSEC trust anchors. The default is the trust anchors from the IANA root. The trust anchors are expressed as RDATAs from DNSKEY resource records.
In the rare case that an application needs to set the DNSSEC skew, it can:
The value is the number of seconds of skew that is allowed in either direction when checking an RRSIG's Expiration and Inception fields. The default is 0.
An application can change the quering mechanism of a context to be to act as a stub resolver. Such an application might first get the default information to make this change from the operating system, probably through DHCP.
Note that if a context is changed to being a stub resolver, this automatically prevents the application
from using the extenstions for DNSSEC. An application that wants to both do DNSSEC and stub resolution
must do its own DNSSEC processing, possibly with the getdns_validate_dnssec()
function.
The list of dicts define where a stub resolver will send queries. Each dict contains
at least two names: address_type
(whose value is a bindata; it is currently either
"IPv4" or "IPv6") and address_data
(whose value is a bindata). It might also contain
port
to specify which port to use to contact these DNS servers; the default is 53. If
the stub and a recursive resolver both support TSIG (RFC 2845), the upstream_list
entry
can also contain tsig_algorithm
(a bindata) that is the name of the TSIG hash
algorithm, and tsig_secret
(a bindata) that is the TSIG key.
These context settings affect queries that have extensions that specify the use of OPT resource records. These come from RFC 6891.
The value is between 512 and 65535; when not set, outgoing values will adhere to the suggestions in RFC 6891 and may follow a scheme that uses multiple values to maximize receptivity.
The value is between 0 and 255; the default is 0.
The value is between 0 and 255; the default is 0.
The value is between 0 and 1; the default is 0.
The given memory management functions will be used for creating the response dicts. The response dicts inherit the custom memory management functions from the context and will deallocate themselves (and their members) with the custom deallocator. By default, the system malloc, realloc, and free are used.
The given extended memory management functions will be used for creating the response dicts.
The value of userarg
argument will be passed to the custom malloc
, realloc
, and free
.
The response dicts inherit the custom memory management functions and the value for userarg
from the context and will deallocate themselves (and their members) with the custom deallocator.
The context codes for getdns_context_set_context_update_callback()
are:
GETDNS_CONTEXT_CODE_NAMESPACES
Change related to getdns_context_set_namespaces
GETDNS_CONTEXT_CODE_RESOLUTION_TYPE
Change related to getdns_context_set_resolution_type
GETDNS_CONTEXT_CODE_FOLLOW_REDIRECTS
Change related to getdns_context_set_follow_redirects
GETDNS_CONTEXT_CODE_UPSTREAM_RECURSIVE_SERVERS
Change related to getdns_context_set_upstream_recursive_servers
GETDNS_CONTEXT_CODE_DNS_ROOT_SERVERS
Change related to getdns_context_set_dns_root_servers
GETDNS_CONTEXT_CODE_DNS_TRANSPORT
Change related to getdns_context_set_dns_transport
GETDNS_CONTEXT_CODE_LIMIT_OUTSTANDING_QUERIES
Change related to getdns_context_set_limit_outstanding_queries
GETDNS_CONTEXT_CODE_APPEND_NAME
Change related to getdns_context_set_append_name
GETDNS_CONTEXT_CODE_SUFFIX
Change related to getdns_context_set_suffix
GETDNS_CONTEXT_CODE_DNSSEC_TRUST_ANCHORS
Change related to getdns_context_set_dnssec_trust_anchors
GETDNS_CONTEXT_CODE_EDNS_MAXIMUM_UDP_PAYLOAD_SIZE
Change related to getdns_context_set_edns_maximum_udp_payload_size
GETDNS_CONTEXT_CODE_EDNS_EXTENDED_RCODE
Change related to getdns_context_set_edns_extended_rcode
GETDNS_CONTEXT_CODE_EDNS_VERSION
Change related to getdns_context_set_edns_version
GETDNS_CONTEXT_CODE_EDNS_DO_BIT
Change related to getdns_context_set_edns_do_bit
GETDNS_CONTEXT_CODE_DNSSEC_ALLOWED_SKEW
Change related to getdns_context_set_dnssec_allowed_skew
GETDNS_CONTEXT_CODE_MEMORY_FUNCTIONS
Change related to getdns_context_set_memory_functions
GETDNS_CONTEXT_CODE_TIMEOUT
Change related to getdns_context_set_timeout
An application might want to see information about the API itself and inspect the current context.
Use the getdns_context_get_api_information
function.
The returned getdns_dict
will contain the following name/value pairs:
version_string
(a bindata) represents the version string for this version of the DNS
API.implementation_string
(a bindata) is a string set by the API implementer. It might
be human-readable, and it might have information in it useful to an application developer (but it doesn't
have to).resolution_type
(an int) is the type of resolver that the API is acting as in this context:
GETDNS_RESOLUTION_RECURSING
or GETDNS_RESOLUTION_STUB
(it will be
a recursing resolver unless the application changed this in a context.all_context
(a dict) with names for all the types of context. This can be used with
getdns_pretty_print_dict() for debugging.There is a tarball that includes the .h files, the examples, and so on. The examples all make, even though there is no API implementation, based on a pseudo-implementation in the tarball; see make-examples-PLATFORM.sh. Note that this currently builds fine on the Macintosh and Ubuntu; help is definitely appreciated on making the build process work on more platforms if it fails there.
The following description of the API may be of value to those who might implement the design, and those who are using an implementation of the design.
The genesis of this DNS API design was seeing other DNS API designs flounder. There are other DNS APIs already available (such as draft-hayatnagarkar-dnsext-validator-api, as well as DNSSEC APIs in BIND and Unbound), but there has been very little uptake of them. In talking to application developers, there was a consistent story: that they felt that the APIs were developed by and for DNS people, not applications developers.
This API design comes from talking to a small handful of applications developers about what they
would want to see in a modern DNS API. Now that the API is public, it would be great to hear from many
more application developers about whether it would meet their needs if it was implemented. My goal
is to create a design that is a natural follow-on to getaddrinfo()
that has all the
capabilities that most application developers might want now or in the next few years: access to all
types of DNS records (including those which are yet to be defined), full DNSSEC awareness, IDN
handling, and parity for IPv4 and IPv6 addresses.
Note that this is just a design for a new API: there is no implementation of the design yet, but at least one is being worked on. The process of designing the API without implementing it at the same time has the huge advantage that major design changes could be made without any worry about "but we already coded it the other way". In the early revisions of this document, many fundamental design choices changed over and over, and even bike-shedding-like changes were allowed because they didn't involve any programming effort.
This work was done independently, not through the IETF because the IETF generally doesn't take on API work, and has explicitly avoided DNS API work in the past.
This API design has a Creative Commons license so that it can be used widely by potential API implementers. This also allows other people who want to fork the design to do so cleanly. Of course, any implementation of this API can choose whatever kind of license the API implementer wishes, but it would be fabulous if one or more such implementations had Creative Commons or BSD-ish licenses.
The API relies heavily on C macros and hopefully has no magic numbers.
All implementations of this API must act as recursive resolvers, and some might choose not to be able to act as stub resolvers. Note that all implementations of this API must be DNSSEC validators.
Because there are many C event libraries available, and they have different calling routines,
it is the implementation of an API that determines which event library is used. This is certainly
not optimal for C programmers, but they appear to have gotten used to is so far. All implementations
of this API must support synchronous calls with getdns_general_sync()
.
Versions are differentiated by version strings instead of version numbers. The version string for this API is "getdns April 2013". Each implementation is free to set the implementation string as it feels fit.
The API's .h file contains a macro called GETDNS_COMPILATION_COMMENT
. This can be useful
to an application which will use the API because it can check the string without calling any
functions. Each time the API implementation is compiled, this string should be updated with unique
information about the implementation build.
The implementation of both the async and sync getdns
functions will
copy all the values of the parameters into local memory, in case the application changes or
deallocates them.
This work is licensed under a Creative Commons Attribution 3.0
Unported License.