s6-dns: the s6dns_engine library interface
Keywords: s6-dns dns s6dns_engine library libs6dns

libs6dns
s6-dns
Software
skarnet.org

# The `s6dns_engine` library interface

The following functions are declared in the `s6-dns/s6dns-engine.h` header, and implemented in the `libs6dns.a` or `libs6dns.so` library.

## General information

`s6dns_engine` is the nitty-gritty of DNS query management. These are the low-level asynchronous primitives sending DNS queries over the network, and getting answers.

`s6dns_engine` has been inspired by Dan J. Bernstein's dns_transmit library, but does not borrow any code from it. Unlike `dns_transmit`, `s6dns_engine` does not assume that network send operations are instant successes; `s6dns_engine` descriptors can be selected for writing as well as for reading. Also, if the underlying skalibs has been compiled with IPv6 support, `s6dns_engine` supports native IPv6 transport.

The `s6dns_engine` functions are used in the implementation of the `s6dns_resolven_loop` function - which is nothing more than a simple event loop around the `s6dns_engine` primitives - and the skadnsd daemon. Both pieces of code are good examples of how to use `s6dns_engine`.

However, unless you're implementing a DNS cache, you probably should not call the `s6dns_engine` functions directly: they are very low-level. If you need synchronous resolution, use the s6dns_resolve functions. If you need asynchronous DNS operations, use the skadns functions, which are designed to provide a higher level interface to multiple asynchronous DNS queries.

## Data structures

A `s6dns_engine_t` structure holds all the data necessary to manage a DNS query (and response). It must be initialized to S6DNS_ENGINE_ZERO when first declared, and recycled with `s6dns_engine_recycle()` after each use. It contains a stralloc, so it must be freed with `s6dns_engine_free()` before being discarded, to avoid memory leaks.

## Functions

### `s6dns_engine_t` life cycle

`int s6dns_engine_init (s6dns_engine_t *dt, s6dns_ip46list_t const *servers, uint32_t options, char const *q, unsigned int qlen, uint16_t qtype, tain_t const *deadline, tain_t const *stamp)`

Initializes dt with query q of length qlen and type qtype. If `d` is an encoded `s6dns_domain_t`, then `d.s` and `d.len` are appropriate candidates for arguments q and qlen respectively.

options can be 0 or an OR'ed combination of the following, defined in `s6-dns/s6dns-constants.h`:

- S6DNS_O_RECURSIVE: the query will be recursive and assuming it is sent to a DNS cache, instead of iterative and assuming it is sent to a DNS server.
- S6DNS_O_STRICT: the library will only accept authoritative answers to iterative queries. This is normally the sane behaviour, but badly configured DNS software around the world - notably, BIND when it's configured to be both a cache and a server - often serve non-authoritative data even when they could, so it breaks things, hence why the option isn't set by default.

servers must point to a list of IP addresses as defined in s6-dns/s6dns-ip46.h. Such a list can be obtained from the `/etc/resolv.conf` file via the s6dns_rci_fill() call when performing a recursive query, or it must be constructed from a list of relevant NS addresses when performing an iterative query.

stamp must be an accurate enough timestamp. deadline sets up a deadline for the query: if the query hasn't been satisfactorily answered by deadline - no matter how many round-trips to network servers the library performs internally - then it will be considered failed, and a timeout will be reported.

The function returns 1 if all went well, and 0 if an error occurred. It returns instantly; it does not perform any network operation, it just prepares dt to send a query. The actual data sending will take place on the next `s6dns_engine_event()` call.

`void s6dns_engine_recycle (s6dns_engine_t *dt)`

Recycles `dt`, making it ready for another use. This function does not deallocate the heap memory used by dt, so it's faster than `s6dns_engine_free()` and does not cause heap fragmentation.

`void s6dns_engine_free (s6dns_engine_t *dt)`

Frees the heap memory used by `dt`. Also makes `dt` ready for another use. It's advised to only use this function when certain that dt will not be reused.

### Before the `iopause()`

The descriptor to select on is available as the `fd` field in the `s6dns_engine_t` structure. dt→fd should be read every iteration, because it can change between iterations even if no event or timeout is reported for dt.

`void s6dns_engine_nextdeadline (s6dns_engine_t const *dt, tain_t *a)`

If dt needs handling before the absolute date *a, then *a is updated so it contains the earlier date. This is useful to compute the next deadline in an `iopause()` loop.

`int s6dns_engine_isreadable (s6dns_engine_t const *dt)`

Returns nonzero iff dt→fd is to be selected for reading. Should be called in every iteration.

`int s6dns_engine_iswritable (s6dns_engine_t const *dt)`

Returns nonzero iff dt→fd is to be selected for writing. Should be called in every iteration.

### After the `iopause()`

`int s6dns_engine_timeout (s6dns_engine_t *dt, tain_t const *stamp)`

This function should be called if your selecting function returned 0, which means that an event timed out. stamp should contain the current time. The function returns -1 if an error occurred, 1 if dt actually timed out, and 0 if nothing special happened to dt (and your iopause timeout was caused by something else). If the return value is not 0, dt is automatically recycled.

`int s6dns_engine_event (s6dns_engine_t *dt, tain_t const *stamp)`

This function should be called if your selecting function returned a positive number, which means that some event got triggered. stamp should contain the current time. The function returns -1 if an error occurred (and dt is automatically recycled). It returns 0 if no answer has arrived yet, and 1 if an answer is available.

The `s6dns_engine_timeout()` and `s6dns_engine_event()` functions, when returning -1, make use of the following error codes:

- EINVAL: Invalid dt.
- ENETUNREACH: All the servers in the servers list have been unsuccessfully tried.
- EPROTO: An answer arrived, but it didn't follow the DNS protocol.
- Other error codes reporting socket or system failures.

`char *s6dns_engine_packet (s6dns_engine_t const *dt)`

Points to the response packet received from the network, if `s6dns_engine_event()` returned 1.

`unsigned int s6dns_engine_packetlen (s6dns_engine_t const *dt)`

Gives the length of the response packet, if `s6dns_engine_event()` returned 1.

You should recycle or free dt after reading the response packet.

`void s6dns_engine_query (s6dns_engine_t const *dt, char **name, uint16_t *len, uint16_t *qtype)`

Recalls the query encoded in *dt: stores a pointer to the encoded name in *name, its length in *len and the query type in *qtype. You can call this function even if dt has been recycled - but, of course, not if it has been freed.