s6-dns: the skadns library interface
Keywords: s6-dns skadns library asynchronous resolution resolver client C interface

s6-dns
Software
skarnet.org

# The `skadns` library interface

The `skadns` library provides an API for asynchronous DNS resolution.

## Compiling

- Make sure the s6-dns headers, as well as the skalibs headers, are visible in your header search path.
- Use `#include <s6-dns/skadns.h>`
- You might also want to include the `s6-dns/s6dns.h` header for the definition of the `s6dns_domain_t` type and the various qtype constants.

## Linking

- Make sure the s6 libraries, as well as the skalibs libraries, are visible in your library search path.
- Link against `-lskadns` and `-lskarnet`.
- If you're using a skadnsd service, also add ``cat $sysdeps/socket.lib`` to the end of your linking command line, `$sysdeps` standing for your skalibs sysdeps directory.

## Programming

Check the `s6-dns/skadns.h` header for the exact function prototypes.

Make sure your application is not disturbed by children it doesn't know it has. This means paying some attention to the SIGCHLD handler, if any, and to the way you perform `waitpid()`s. The best practice is to use a self-pipe to handle SIGCHLD (as well as other signals the application needs to trap), and to always use `wait_nohang()` to reap children, simply ignoring pids you don't know.

If your (badly programmed) application has trouble handling unknown children, consider using a skadnsd service.

### A programming example

The `src/clients/s6dns_generic_filter_main.c` file in the s6-dns package, used in the `s6-dns*-filter` programs, illustrates how to use the skadns library.

### Starting and ending a session

    skadns_t a = SKADNS_ZERO ;
    tain_t deadline, stamp ;

    tain_now(&stamp) ;
    tain_addsec(&deadline, &stamp, 2)

    // char const *path = SKADNS_IPCPATH ;
    // skadns_start(&a, path, &deadline, &stamp) ;
    skadns_startf(&a, &deadline, &stamp) ;

`skadns_start` starts a session with a skadnsd service, listening on path.
`skadns_startf` starts a session with a skadnsd process as a child (which is the simplest usage).
`a` is a skadns_t structure that must be declared and initialized to SKADNS_ZERO. `stamp` must be an accurate enough timestamp.
If the session initialization fails, the function returns 0 and errno is set; else the function returns 1.

If the absolute time `deadline` is reached and the function has not returned yet, it immediately returns 0 with errno set to ETIMEDOUT. Only local interprocess communications are involved; unless your system is heavily overloaded, the function should return near-instantly. One or two seconds of delay between `stamp` and `deadline` should be enough: if the function takes more than that to return, then there is a problem with the underlying processes.

You can have more than one session open in parallel, by declaring several distinct `skadns_t` structures and calling `skadns_startf` (or `skadns_start`) more than once. However, this is only useful if you need to perform more than SKADNS_MAXCONCURRENCY, i.e. a thousand, concurrent requests. In most situations, a single skadns session will be enough.

    skadns_end(&a) ;

`skadns_end` frees all the resources used by the session. The `a` structure is then reusable for another session.

### Sending a DNS query

    s6dns_domain_t d ;
    uint16_t qtype ;
    uint16_t id ;
    tain_t limit, deadline, stamp ;

    skadns_send(&a, &id, &d, qtype, &limit, &deadline, &stamp) ;

`skadns_send` starts the asynchronous resolution of domain d with query type qtype. d must be encoded in packet form. stamp must be an accurate enough timetamp. If the resolution hasn't completed by deadline limit, it will automatically fail with a status set to ETIMEDOUT.

Like `skadns_startf()`, the `skadns_send()` call is synchronous but should not be blocking; however, if it hasn't returned by deadline deadline, it then returns 0 with errno set to ETIMEDOUT. On failure, the call returns 0 and sets errno. On success, it returns 1 and id is set to a 16-bit number identifying the query.

### Cancelling a query

    skadns_cancel(&a, id, &deadline, &stamp) ;

`skadns_cancel` cancels the resolution identified by id. stamp must be an accurate enough timestamp. The call returns 1 on success, or 0 (and sets errno) on failure. It is synchronous but should return almost-instantly; if it hasn't returned by deadline, it then returns 0 ETIMEDOUT.

After a query has been successfully canceled, its id can be discarded.

### Asynchronously waiting for answers

(from now on, the functions are listed with their prototypes instead of usage examples.)

    int skadns_fd (skadns_t const *a)

Returns a file descriptor to select on for reading. Do not `read()` it though.

    int skadns_update (skadns_t *a)

Call this function whenever the fd checks readability: it will update a's internal structures with information from the skadnsd daemon. It returns -1 if an error occurs; in case of success, it returns the number of identifiers for which something happened.

When `skadns_update` returns, `genalloc_s(uint16_t, &a->list)` points to an array of `genalloc_len(uint16_t, &a->list)` 16-bit unsigned integers. Those integers are valid ids that can be used with the following functions.

    char const *skadns_packet (skadns_t *a, uint16_t id)
    int skadns_packetlen (skadns_t *a, uint16_t id)

`skadns_packet()` returns a pointer to the DNS packet containing the answer to the query identified by id, and `skadns_packetlen()` returns its length. If an error has occurred, `skadns_packet()` returns NULL and `skadns_packetlen()` returns -1; either call sets errno to a value identifying the error. Some errno values have a special meaning:

- EAGAIN: the result has not arrived yet.
- ECANCELED: the query has been canceled.
- EINVAL: id does not identify a valid query. Note that if you get EINVAL, then id is invalid, but the reverse is not true: using invalid ids is a programming error and may result in a crash.

    int skadns_release (skadns_t *a, uint16_t id)

`skadns_release()` frees the cell holding the result of query id. It returns 1 on success, then id can be discarded - further calls to `skadns_send()` may reuse the same number.

`skadns_release()` may fail, and return 0. This signals a programming error and shouldn't be relied on - however, if it happens, errno can help you identify the problem:

- EBUSY: the query is still pending, cancelled or not.
- EINVAL: id is invalid.