summaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2014-12-10 03:05:47 +0000
committerLaurent Bercot <ska-skaware@skarnet.org>2014-12-10 03:05:47 +0000
commit416ef5e2bf59bb2e45066a1d5d91ac677c0f48e5 (patch)
tree1c746d673dcec7a8488c6ac51db8245411034376 /doc
downloads6-dns-416ef5e2bf59bb2e45066a1d5d91ac677c0f48e5.tar.xz
Initial commit
Diffstat (limited to 'doc')
-rw-r--r--doc/getaddrinfo.html94
-rw-r--r--doc/index.html175
-rw-r--r--doc/libresolv.html140
-rw-r--r--doc/libs6dns/index.html104
-rw-r--r--doc/libs6dns/s6dns-domain.html137
-rw-r--r--doc/libs6dns/s6dns-engine.html255
-rw-r--r--doc/libs6dns/s6dns-fmt.html92
-rw-r--r--doc/libs6dns/s6dns-ip46.html69
-rw-r--r--doc/libs6dns/s6dns-message.html261
-rw-r--r--doc/libs6dns/s6dns-rci.html109
-rw-r--r--doc/libs6dns/s6dns-resolve.html387
-rw-r--r--doc/s6-dnsip4-filter.html111
-rw-r--r--doc/s6-dnsip4.html63
-rw-r--r--doc/s6-dnsip6-filter.html99
-rw-r--r--doc/s6-dnsip6.html73
-rw-r--r--doc/s6-dnsmx.html64
-rw-r--r--doc/s6-dnsname-filter.html103
-rw-r--r--doc/s6-dnsname.html72
-rw-r--r--doc/s6-dnsns.html63
-rw-r--r--doc/s6-dnsq.html82
-rw-r--r--doc/s6-dnsqr.html80
-rw-r--r--doc/s6-dnsqualify.html41
-rw-r--r--doc/s6-dnssoa.html65
-rw-r--r--doc/s6-dnssrv.html65
-rw-r--r--doc/s6-dnstxt.html77
-rw-r--r--doc/s6-randomip.html57
-rw-r--r--doc/skadns/index.html262
-rw-r--r--doc/skadns/skadnsd.html120
-rw-r--r--doc/upgrade.html32
29 files changed, 3352 insertions, 0 deletions
diff --git a/doc/getaddrinfo.html b/doc/getaddrinfo.html
new file mode 100644
index 0000000..da0469c
--- /dev/null
+++ b/doc/getaddrinfo.html
@@ -0,0 +1,94 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the problem with getaddrinfo()</title>
+ <meta name="Description" content="s6-dns: the problem with getaddrinfo()" />
+ <meta name="Keywords" content="s6-dns client library getaddrinfo API interface" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The problem with getaddrinfo() </h1>
+
+<p>
+ The standard C library provides an API to perform name
+resolution:
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/getaddrinfo.html">getaddrinfo()</a>,
+formerly <tt>gethostbyname()</tt>. However, for DNS resolution as well as
+implementation in the libc, this interface is very impractical, to the point of
+being unusable. Here are a few reasons why.
+</p>
+
+<h2> getaddrinfo() performs NSS resolution, not DNS resolution. </h2>
+
+<p>
+ I explained this point in a message to the
+<a href="http://busybox.net/">Busybox</a> mailing-list. You can
+<a href="http://lists.busybox.net/pipermail/busybox/2012-July/078123.html">read
+the post here</a>.
+(There is a mistake in that post about <tt>/etc/nsswitch.conf</tt> and
+<tt>/etc/host.conf</tt> syntax; the following two messages in the thread
+correct that mistake.)
+</p>
+
+<p>
+ TLDR: depending on the machine configuration, it is possible that
+<tt>getaddrinfo()</tt> will not use DNS at all.
+</p>
+
+<h2> It is unboundedly synchronous. </h2>
+
+<p>
+ DNS resolution performs network I/O, which can take a non-negligible
+amount of time. <tt>getaddrinfo()</tt> is a blocking call and there is
+no way to specify a timeout to make it return early, so it may block
+indefinitely. This is bad design.
+</p>
+
+<p>
+ Also, network operations being asynchronous by nature, even a
+synchronous API should provide a way to perform several queries at
+once and return when one of them, or all of them, get an answer.
+<tt>getaddrinfo()</tt> does not even offer that.
+</p>
+
+<h2> It focuses on the wrong details. </h2>
+
+<p>
+ Because it's generic, <tt>getaddrinfo()</tt> is cumbersome to use.
+ The data structures are impractical, requiring the user to fill in
+information that is irrelevant to DNS resolution. The details of the
+network transport protocols are of no interest to the user who just
+wants answers to his DNS queries!
+</p>
+
+<p>
+ But at the same time, <tt>getaddrinfo()</tt> does not allow the
+user to provide the details he wants or refine his search. It's a very
+basic and monolithic entry point, with no DNS-specific knobs. For
+instance, only A and AAAA queries are supported, which is clearly
+insufficient.
+</p>
+
+<h2> Conclusion </h2>
+
+<p>
+<tt>getaddrinfo()</tt> is a toy interface. For any half-serious DNS work,
+another API must be used.
+</p>
+
+<p>
+ Most people who need a real DNS client library use BIND's libresolv.
+<a href="libresolv.html">This page</a> explains what is wrong with it,
+and what s6-dns tries to do better.
+</p>
+
+</body>
+</html>
diff --git a/doc/index.html b/doc/index.html
new file mode 100644
index 0000000..5263664
--- /dev/null
+++ b/doc/index.html
@@ -0,0 +1,175 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns - a complete DNS client suite</title>
+ <meta name="Description" content="s6-dns - a complete DNS client suite" />
+ <meta name="Keywords" content="s6-dns s6dns s6 DNS resolution unix linux laurent bercot ska skarnet" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> s6-dns </h1>
+
+<h2> What is it&nbsp;? </h2>
+
+<p>
+ s6-dns is a suite of DNS client programs and libraries for Unix
+systems, as an alternative to the BIND, djbdns or other DNS clients.
+</p>
+
+<p>
+ s6-dns may include its own series of DNS caches and servers at some
+point in the future.
+</p>
+
+<hr />
+
+<ul>
+<li> <a href="getaddrinfo.html">Why a separate client library&nbsp;?</a> Why not just use <tt>getaddrinfo()</tt>&nbsp;?</li>
+<li> <a href="libresolv.html">What is wrong with libresolv, BIND's client library&nbsp;?</a></li>
+</ul>
+
+<hr />
+
+<h2> Installation </h2>
+
+<h3> Requirements </h3>
+
+<ul>
+ <li> A POSIX-compliant system with a standard C development environment </li>
+ <li> GNU make, version 3.81 or later </li>
+ <li> <a href="http://skarnet.org/software/skalibs/">skalibs</a> version
+2.0.0.0 or later </li>
+</ul>
+
+<h3> Licensing </h3>
+
+<p>
+ s6-dns is free software. It is available under the
+<a href="http://opensource.org/licenses/ISC">ISC license</a>.
+</p>
+
+<h3> Download </h3>
+
+<ul>
+ <li> The current released version of s6-dns is <a href="s6-dns-2.0.0.0.tar.gz">2.0.0.0</a>. </li>
+ <li> Alternatively, you can checkout a copy of the s6-dns git repository:
+<pre> git clone git://git.skarnet.org/s6-dns </pre> </li>
+</ul>
+
+<h3> Compilation </h3>
+
+<ul>
+ <li> See the enclosed INSTALL file for installation details. </li>
+</ul>
+
+<h3> Upgrade notes </h3>
+
+<ul>
+ <li> <a href="upgrade.html">This page</a> lists the differences to be aware of between
+the previous versions of s6-dns and the current one. </li>
+</ul>
+
+<h2> Reference </h2>
+
+<h3> Commands </h3>
+
+<p>
+ All these commands exit 111 if they encounter a temporary error or
+hardware error, and
+100 if they encounter a permanent error - such as a misuse. Short-lived
+commands exit 0 on success. Other exit codes are documented in the
+relevant page.
+</p>
+
+<h4> Command-line DNS clients programs </h4>
+
+<ul>
+<li><a href="s6-dnsip4.html">The <tt>s6-dnsip4</tt> program</a></li>
+<li><a href="s6-dnsip6.html">The <tt>s6-dnsip6</tt> program</a></li>
+<li><a href="s6-dnsname.html">The <tt>s6-dnsname</tt> program</a></li>
+<li><a href="s6-dnsmx.html">The <tt>s6-dnsmx</tt> program</a></li>
+<li><a href="s6-dnsns.html">The <tt>s6-dnsns</tt> program</a></li>
+<li><a href="s6-dnssoa.html">The <tt>s6-dnssoa</tt> program</a></li>
+<li><a href="s6-dnssrv.html">The <tt>s6-dnssrv</tt> program</a></li>
+<li><a href="s6-dnstxt.html">The <tt>s6-dnstxt</tt> program</a></li>
+</ul>
+
+<h4> Filtering tools </h4>
+
+<ul>
+<li><a href="s6-dnsip4-filter.html">The <tt>s6-dnsip4-filter</tt> program</a></li>
+<li><a href="s6-dnsip6-filter.html">The <tt>s6-dnsip6-filter</tt> program</a></li>
+<li><a href="s6-dnsname-filter.html">The <tt>s6-dnsname-filter</tt> program</a></li>
+</ul>
+
+<h4> Command-line qualification </h4>
+
+<ul>
+<li><a href="s6-dnsqualify.html">The <tt>s6-dnsqualify</tt> program</a></li>
+</ul>
+
+<h4> DNS analysis and debug tools </h4>
+
+<ul>
+<li><a href="s6-dnsq.html">The <tt>s6-dnsq</tt> program</a></li>
+<li><a href="s6-dnsqr.html">The <tt>s6-dnsqr</tt> program</a></li>
+</ul>
+
+<h4> Miscellaneous utilities </h4>
+
+<ul>
+<li><a href="s6-randomip.html">The <tt>s6-randomip</tt> program</a></li>
+</ul>
+
+
+<h3> Libraries </h3>
+
+<h4> Protocol implementation and synchronous resolution </h4>
+
+<ul>
+<li><a href="libs6dns/">The <tt>s6dns</tt> library interface</a></li>
+</ul>
+
+<h4> Asynchronous resolution </h4>
+
+<ul>
+<li><a href="skadns/">The <tt>skadns</tt> library interface</a></li>
+</ul>
+
+<hr />
+
+<a name="related">
+<h2> Related resources </h2>
+</a>
+
+<h3> s6-dns discussion </h3>
+
+<ul>
+ <li> <tt>s6-dns</tt> is discussed on the
+<a href="http://skarnet.org/lists.html#skaware">skaware</a> mailing-list. </li>
+ <li> It can also be discussed on the
+<a href="http://cr.yp.to/lists.html#dns">cr.yp.to dns mailing-list</a>. </li>
+</ul>
+
+<h3> Similar work </h3>
+
+<ul>
+ <li> <a href="http://www.isc.org/software/bind">BIND</a> is the most widely
+used DNS software suite on the Internet. It's also arguably the most
+<a href="http://cr.yp.to/djbdns/blurb/unbind.html">buggy</a>,
+convoluted and impossible to understand. </li>
+ <li> <a href="http://cr.yp.to/djbdns.html">djbdns</a> is DJB's DNS suite.
+It works very well, but is unfortunately unmaintained by the author. s6-dns
+follows <a href="http://skarnet.org/software/skalibs/djblegacy.html">the
+same design principles</a>. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/libresolv.html b/doc/libresolv.html
new file mode 100644
index 0000000..7a9fd31
--- /dev/null
+++ b/doc/libresolv.html
@@ -0,0 +1,140 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the problem with libresolv</title>
+ <meta name="Description" content="s6-dns: the problem with libresolv" />
+ <meta name="Keywords" content="s6-dns client library libresolv BIND API interface" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The problem with libresolv </h1>
+
+<p>
+ The BIND name server software comes with its own client library,
+named <em>libresolv</em>.
+</p>
+
+<p>
+ As can be expected from an ISC product, libresolv is not good software.
+Here are a few reasons why.
+</p>
+
+<h2> libresolv's security model is flawed. </h2>
+
+<p>
+ The same people who wrote BIND wrote libresolv. That is the amount of
+trust you can place in libresolv. Ten years ago, the security status
+of libresolv looked like
+<a href="http://cr.yp.to/djbdns/res-disaster.html">this</a>. I am not
+confident that is has improved: bugs in the software may have been
+fixed, but new ones will appear, and most importantly, the security
+management policy at ISC is still the same: security holes will be
+denied instead of acknowledged and worked upon.
+</p>
+
+<p>
+ If you find a bug, and <em>a fortiori</em> a security hole, in
+s6-dns, you can be sure it will be fixed promptly with apologies
+from the author. skarnet.org doesn't do obfuscation, and never lets
+politics get in the way of technical quality.
+</p>
+
+<h2> libresolv is unboundedly synchronous. </h2>
+
+<p>
+ You'd expect a real DNS client library to do better in this aspect
+than <a href="getaddrinfo.html">getaddrinfo()</a>, but no: libresolv's
+function calls are still purely synchronous and may uncontrollably
+block if the network is unresponsive.
+</p>
+
+<p>
+ Additionally, libresolv <em>only</em> provides a synchronous
+interface to clients. Despite the fundamentally asynchronous nature
+of DNS, and the need to implement asynchronous primitives
+internally, only blocking calls are made available in the API.
+This forces users to stack yet another piece of software on top
+of their dependencies if they need asynchronous DNS resolution.
+</p>
+
+<p>
+ libs6dns provides several layers of asynchronous interfaces.
+The user has access to
+<a href="libs6dns/s6dns_engine.html">low-level packet sending
+and receiving</a>, to
+<a href="libs6dns/s6dns_resolve.html#parallel">synchronous
+resolution of several queries at once</a>, and to a
+<a href="skadns/">real high-level asynchronous DNS library</a>.
+</p>
+
+<h2> It is too big for what it does. </h2>
+
+<p>
+ The <tt>libresolv-2.13.so</tt> binary file compiled for an i386
+Debian Linux system is roughly 71k bytes. The <tt>libs6dns.so.2.0</tt>
+file, for the same system, is roughly 41k bytes, while offering
+more functionality. libresolv does not do any high-level answer
+parsing, so the user still has to do some work after the libresolv
+calls. s6-dns tries to be small and still provide the user comfortable
+interfaces.
+</p>
+
+<h2> The API is cumbersome to use. </h2>
+
+<p>
+ Some examples of less-than-ideal interfaces for the users:
+</p>
+
+<ul>
+ <li> There is a flag in libresolv's global state structure that
+determines if queries are to be sent recursive or iterative. Which
+means that a program that needs to make both recursive and iterative
+queries must duplicate this state structure and use one for recursive
+queries, one for iterative queries. Why this complexity&nbsp;? The
+recursive flag should be given for every query, not be a part
+of the resolver state. </li>
+ <li> When a libresolv answer arrives but doesn't fit into the
+user-supplied buffer, the query is discarded and has to be retried.
+Does "network efficiency" ring a bell&nbsp;? Also, the length of
+the original answer is returned by the first call, which is a good
+thing: the user can now provide a large enough buffer so the call
+succeeds the next time, right&nbsp;? Wrong. The next answer can
+be different from the first, and in particular, longer, which
+means that the next query can <em>still fail</em>. </li>
+</ul>
+
+<p>
+ There is a reason why system calls and local functions take
+user-supplied buffers as arguments. They are relatively fast, it is
+not too costly to call them again if the buffer is too small the
+first time, and the result is consistent, i.e. after the first call,
+the right buffer length is <em>known</em>. But functions making
+network exchanges with variable-length results from one call to
+another&nbsp;? Those <em>need</em> heap-allocated storage. It is
+good design to avoid using the heap whenever possible, but it is
+not good design to waste network round-trips to save a <tt>malloc()</tt>.
+</p>
+
+<h2> Conclusion </h2>
+
+<p>
+ Like many other "standards", and C library interfaces in particular,
+<tt>libresolv</tt> is at best a mediocre one, that people use because
+there has been nothing better so far.
+</p>
+
+<p>
+ s6-dns tries to be an alternative solution - not as ambitious,
+but based on solid design principles and a reliable code base.
+</p>
+
+</body>
+</html>
diff --git a/doc/libs6dns/index.html b/doc/libs6dns/index.html
new file mode 100644
index 0000000..db7ee35
--- /dev/null
+++ b/doc/libs6dns/index.html
@@ -0,0 +1,104 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6dns library interface</title>
+ <meta name="Description" content="s6-dns: the s6dns library interface" />
+ <meta name="Keywords" content="s6-dns dns s6dns library libs6dns" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="../">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6dns</tt> library interface </h1>
+
+<h2> General information </h2>
+
+<p>
+ <tt>libs6dns</tt> is a DNS client library, designed for clarity
+and simplicity - which translates into smallness of the code.
+</p>
+
+<p>
+ A major focus of <tt>libs6dns</tt> is to avoid unnecessary use
+of heap memory. Memory is only allocated in the heap to store
+queries and response packets during a DNS resolution process, and
+to store the final answers into a user-provided
+<a href="http://skarnet.org/software/skalibs/libstddjb/stralloc.html">
+stralloc</a>; all the other operations use stack memory, and perform
+as few copies as possible.
+</p>
+
+<h2> Compiling </h2>
+
+<ul>
+ <li> Make sure the s6-dns headers, as well as the skalibs headers,
+are visible in your header search path. </li>
+ <li> Use <tt>#include &lt;s6-dns/s6dns.h&gt;</tt> </li>
+</ul>
+
+<h2> Linking </h2>
+
+<ul>
+ <li> Make sure the s6-dns libraries, as well as the skalibs libraries,
+are visible in your library search path. </li>
+ <li> Link against <tt>-ls6dns</tt>, <tt>-lskarnet</tt>, </li>
+<tt>`cat $SYSDEPS/socket.lib`</tt> and
+<tt>`cat $SYSDEPS/tainnow.lib`</tt>, $SYSDEPS being your skalibs
+sysdeps directory. </li>
+</ul>
+
+<h2> Programming </h2>
+
+<p>
+ The <tt>s6dns.h</tt> header is actually a concatenation of other headers:
+the libs6dns is separated into several modules, each of them with its
+own header.
+</p>
+
+<ul>
+ <li> The <tt>s6dns-constants.h</tt> header provides constants used in
+other parts of the library. </li>
+ <li> The <a href="s6dns-ip46.html">s6dns-ip46.h</a> header provides an
+abstraction for IPv4 and IPv6 transports. </li>
+ <li> The <a href="s6dns-domain.html">s6dns-domain.h</a> header provides
+basic string manipulation primitives for domain names. </li>
+ <li> The <a href="s6dns-message.html">s6dns-message.h</a> header provides
+function to parse a message following the DNS protocol. </li>
+ <li> The <a href="s6dns-engine.html">s6dns-engine.h</a> header provides
+the low-level asynchronous networking functions. </li>
+ <li> The <a href="s6dns-rci.html">s6dns-rci.h</a> header provides an
+interface to <tt>resolv.conf</tt> reading. </li>
+ <li> The <a href="s6dns-resolve.html">s6dns-resolve.h</a> header provides
+the user-level synchronous resolution functions. </li>
+ <li> The <a href="s6dns-fmt.html">s6dns-fmt.h</a> header provides
+formatting primitives to display RR contents. </li>
+</ul>
+
+<p>
+ (User-level asynchronous resolution functions are provided in the
+<a href="../skadns/">skadns</a> library.)
+</p>
+
+<p>
+ Two functional macros are actually directly declared in the <tt>s6dns.h</tt>
+header:
+</p>
+
+<ul>
+ <li> Call <tt>s6dns_init()</tt> before all your s6dns operations.
+s6dns_init() calls <a href="s6dns-rci.html">s6dns_rci_init()</a>,
+extracting <tt>resolv.conf</tt> information to an internal global
+variable. The function returns 1 on success, and 0 (and sets errno)
+on failure. </li>
+ <li> Call <tt>s6dns_finish()</tt> when you're done with the libs6dns.
+It frees the resources used. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/libs6dns/s6dns-domain.html b/doc/libs6dns/s6dns-domain.html
new file mode 100644
index 0000000..b1492c3
--- /dev/null
+++ b/doc/libs6dns/s6dns-domain.html
@@ -0,0 +1,137 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6dns_domain library interface</title>
+ <meta name="Description" content="s6-dns: the s6dns_domain library interface" />
+ <meta name="Keywords" content="s6-dns dns s6dns_domain library libs6dns" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">libs6dns</a><br />
+<a href="../">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6dns_domain</tt> library interface </h1>
+
+<p>
+ The following functions and structures are declared in the <tt>s6-dns/s6dns-domain.h</tt> header,
+and implemented in the <tt>libs6dns.a</tt> or <tt>libs6dns.so</tt> library.
+</p>
+
+<h2> General information </h2>
+
+<p>
+ <tt>s6dns_domain</tt> provides primitives to perform basic operations
+on domain names.
+</p>
+
+<h2> Data structures </h2>
+
+<p>
+ A <tt>s6dns_domain_t</tt> is a structure that s6dns uses internally to
+represent a domain name. It can be in <em>string form</em> (or
+<em>decoded form</em>), which is close to the printable form seen
+by users, or in <em>packet form</em> (or <em>encoded form</em>), which
+is the format used in the DNS protocol.
+</p>
+
+<p>
+ A <tt>s6dns_domain_t</tt> is a flat structure and can be declared
+on the stack.
+</p>
+
+<h2> Functions </h2>
+
+<p>
+<code> int s6dns_domain_fromstring (s6dns_domain_t *d, char const *s, unsigned int len) </code> <br />
+Makes a (string form) domain from string <em>s</em> of length <em>len</em>,
+and stores it into *<em>d</em>. Returns 1 if it succeeds, otherwise it
+returns 0 and sets errno appropriately - most likely ENAMETOOLONG, i.e.
+the name in <em>s</em> is not a well-formed domain name.
+</p>
+
+<p>
+<code> unsigned int s6dns_domain_tostring (char *s, unsigned int max, s6dns_domain_t const *d) </code> <br />
+Writes into string <em>s</em> the domain contained in *<em>d</em> (in string
+form). Returns the number of bytes written, or 0 in case of failure. If
+the output would be more than <em>max</em> bytes, 0 ENAMETOOLONG is returned.
+</p>
+
+<p>
+<code> int s6dns_domain_noqualify (s6dns_domain_t *d) </code> <br />
+Makes sure that *<em>d</em> is fully qualified. This is done without using
+qualification: a trailing dot is simply appended if the domain
+doesn't already have one. Returns 1 if it succeeds, or 0 if it fails.
+</p>
+
+<p>
+<code> unsigned int s6dns_domain_qualify (s6dns_domain_t *list, s6dns_domain_t const *d, char const *rules, unsigned int rulesnum) </code> <br />
+Performs qualification on domain *<em>d</em> according to the first
+<em>rulesnum</em> rules stored in string <em>rules</em>. Stores the output
+(at most <em>rulesnum</em> domains) into the array pointed to by <em>list</em>.
+Returns 0 on failure, or the number of written domains if it succeeds. This
+number can be lesser than <em>rulesnum</em>, for instance if *<em>d</em> is
+already fully qualified. <br />
+Valid <em>rules</em> and <em>rulesnum</em> can be obtained via functions
+declared in <a href="s6dns-rci.html">s6dns-rci.h</a>.
+</p>
+
+<p>
+<code> int s6dns_domain_encode (s6dns_domain_t *d) </code> <br />
+Encodes domain *<em>d</em> from string form to packet form. Returns 1
+if it succeeds or 0 if it fails - for instance, *<em>d</em> is not a
+valid string form (EINVAL).
+</p>
+
+<p>
+<code> unsigned int s6dns_domain_encodelist (s6dns_domain_t *list, unsigned int n) </code> <br />
+Encodes <em>n</em> domains pointed to by <em>list</em> from string form to packet form,
+stopping at the first failure.
+Returns the number of successfully encoded domains, normally <em>n</em>.
+</p>
+
+<p>
+<code> int s6dns_domain_decode (s6dns_domain_t *d) </code> <br />
+Decodes domain *<em>d</em> from packet form to string form. Returns 1
+if it succeeds or 0 if it fails - for instance, *<em>d</em> is not a
+valid packet form (EPROTO).
+</p>
+
+<p>
+<code> int s6dns_domain_fromstring_noqualify_encode (s6dns_domain_t *d, char const *s, unsigned int len) </code> <br />
+Higher-level function wrapping some of the above. Makes an encoded, fully qualified
+(without resorting to qualification) domain from string <em>s</em> of
+length <em>len</em>. Returns 1 if it succeeds and 0 if it fails.
+</p>
+
+<p>
+<code> unsigned int s6dns_domain_fromstring_qualify_encode (s6dns_domain_t *list, char const *s, unsigned int len, char const *rules, unsigned int rulesnum) </code> <br />
+Another wrapping function. It makes a list of encoded, fully qualified domains,
+from string <em>s</em> of length <em>len</em> using <em>rulesnum</em> qualification
+rules in <em>rules</em>. It writes at most <em>rulesnum</em> domains into the array
+pointed to by <em>list</em> and returns the number of written domains, or 0 on an
+error.
+</p>
+
+<p>
+<code> void s6dns_domain_arpafromip4 (s6dns_domain_t *d, char const *ip) </code> <br />
+Writes into <em>d</em> the <tt>in-addr.arpa.</tt> domain corresponding to IPv4 address
+<em>ip</em> (4 bytes, in network byte order), in string form.
+</p>
+
+<p>
+<code> void s6dns_domain_arpafromip6 (s6dns_domain_t *d, char const *ip, unsigned int mask) </code> <br />
+Writes into <em>d</em> the <tt>ip6.arpa.</tt> domain corresponding to the first
+<em>mask</em> bits of IPv6 address <em>ip</em> (16 bytes, in network byte order),
+in string form.
+Only multiples of 4 count for <em>mask</em>, i.e. the output will be the same
+if mask is (for instance) 125, 126, 127 or 128.
+</p>
+
+</body>
+</html>
diff --git a/doc/libs6dns/s6dns-engine.html b/doc/libs6dns/s6dns-engine.html
new file mode 100644
index 0000000..f0f9573
--- /dev/null
+++ b/doc/libs6dns/s6dns-engine.html
@@ -0,0 +1,255 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6dns_engine library interface</title>
+ <meta name="Description" content="s6-dns: the s6dns_engine library interface" />
+ <meta name="Keywords" content="s6-dns dns s6dns_engine library libs6dns" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">libs6dns</a><br />
+<a href="../">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6dns_engine</tt> library interface </h1>
+
+<p>
+ The following functions are declared in the <tt>s6-dns/s6dns-engine.h</tt> header,
+and implemented in the <tt>libs6dns.a</tt> or <tt>libs6dns.so</tt> library.
+</p>
+
+<h2> General information </h2>
+
+<p>
+ <tt>s6dns_engine</tt> is the nitty-gritty of DNS query management. These
+are the low-level asynchronous primitives sending DNS queries over the
+network, and getting answers.
+</p>
+
+<p>
+ <tt>s6dns_engine</tt> has been inspired by Dan J. Bernstein's
+<a href="http://cr.yp.to/djbdns/dns_transmit.html">dns_transmit</a>
+library, but does not borrow any code from it. Unlike
+<tt>dns_transmit</tt>, <tt>s6dns_engine</tt> does not assume that
+network send operations are instant successes; <tt>s6dns_engine</tt>
+descriptors can be selected for writing as well as for reading.
+Also, if the underlying <a href="http://skarnet.org/software/skalibs">
+skalibs</a> has been compiled with IPv6 support, <tt>s6dns_engine</tt>
+supports native IPv6 transport.
+</p>
+
+<p>
+ The <tt>s6dns_engine</tt> functions are used in the implementation of the
+<tt>s6dns_resolven_loop</tt> function - which is nothing more than a
+simple event loop around the <tt>s6dns_engine</tt> primitives - and the
+<a href="../skadns/skadnsd.html">skadnsd</a> daemon. Both pieces of code are
+good examples of how to use <tt>s6dns_engine</tt>.
+</p>
+
+<p>
+ However, unless you're implementing a DNS cache, you probably should
+not call the
+<tt>s6dns_engine</tt> functions directly: they are very low-level. If you
+need synchronous resolution, use the
+<a href="s6dns-resolve.html">s6dns_resolve</a> functions. If you need
+asynchronous DNS operations, use the
+<a href="../skadns/index.html">skadns</a> functions, which are
+designed to provide a higher level interface to multiple asynchronous
+DNS queries.
+</p>
+
+<h2> Data structures </h2>
+
+<p>
+ A <tt>s6dns_engine_t</tt> 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 <tt>s6dns_engine_recycle()</tt>
+after each use. It contains a stralloc, so it must be freed with
+<tt>s6dns_engine_free()</tt> before being discarded, to avoid memory leaks.
+</p>
+
+<h2> Functions </h2>
+
+<h3> <tt>s6dns_engine_t</tt> life cycle </h3>
+
+<p>
+<code> int s6dns_engine_init (s6dns_engine_t *dt, s6dns_ip46list_t const *servers, uint32 options, char const *q, unsigned int qlen, uint16 qtype, struct taia const *deadline, struct taia const *stamp) </code>
+</p>
+
+<p>
+Initializes <em>dt</em> with query <em>q</em> of length <em>qlen</em>
+and type <em>qtype</em>. If <tt>d</tt> is an
+encoded <tt>s6dns_domain_t</tt>, then <tt>d.s</tt> and <tt>d.len</tt>
+are appropriate candidates for arguments <em>q</em> and <em>qlen</em>
+respectively.
+</p>
+
+<p>
+<em>options</em> can be 0 or an OR'ed
+combination of the following, defined in <tt>s6-dns/s6dns-constants.h</tt>:
+</p>
+
+<ul>
+ <li> 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. </li>
+ <li> 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, <a href="../bind.html">
+BIND</a> when it's configured to be both a cache and a server - often
+serve <em>non-</em>authoritative data even when they could, so it
+breaks things, hence why the option isn't set by default. </li>
+</ul>
+
+<p>
+<em>servers</em> must point to a list of IP addresses as defined in
+<a href="s6dns-ip46.html">s6-dns/s6dns-ip46.h</a>. Such a list can be
+obtained from the <tt>/etc/resolv.conf</tt> file via the
+<a href="s6dns-rci.html">s6dns_rci_fill()</a> call when performing a
+recursive query, or it must be constructed from a list of relevant
+NS addresses when performing an iterative query.
+</p>
+
+<p>
+<em>stamp</em> must be an accurate enough timestamp. <em>deadline</em>
+sets up a deadline for the query: if the query hasn't been
+satisfactorily answered by <em>deadline</em> - 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.
+</p>
+
+<p>
+The function returns 1 if all went well, and 0 if an error occurred.
+It returns instantly; it <em>does not</em> perform any network operation,
+it just prepares <em>dt</em> to send a query. The actual data sending
+will take place on the next <tt>s6dns_engine_event()</tt> call.
+</p>
+
+<p>
+<code> void s6dns_engine_recycle (s6dns_engine_t *dt) </code>
+</p>
+
+<p>
+Recycles <tt>dt</tt>, making it ready for another use. This function
+does not deallocate the heap memory used by dt, so it's faster than
+<tt>s6dns_engine_free()</tt> and does not cause heap fragmentation.
+</p>
+
+<p>
+<code> void s6dns_engine_free (s6dns_engine_t *dt) </code>
+</p>
+
+<p>
+Frees the heap memory used by <tt>dt</tt>. Also makes <tt>dt</tt>
+ready for another use. It's advised to only use this function when
+certain that <em>dt</em> will not be reused.
+</p>
+
+<h3> Before the <tt>iopause()</tt> </h3>
+
+<p>
+ The descriptor to select on is available as the <tt>fd</tt> field in
+the <tt>s6dns_engine_t</tt> structure.
+<em>dt</em>&rarr;fd should be read every iteration, because it can
+change between iterations even if no event or timeout is reported
+for <em>dt</em>.
+</p>
+
+<p>
+<code> void s6dns_engine_nextdeadline (s6dns_engine_t const *dt, struct taia *a) </code>
+</p>
+
+<p>
+If <em>dt</em> needs handling before the absolute date *<em>a</em>,
+then *<em>a</em> is updated
+so it contains the earlier date. This is useful to compute the next
+deadline in an <tt>iopause()</tt> loop.
+</p>
+
+<p>
+<code> int s6dns_engine_isreadable (s6dns_engine_t const *dt) </code>
+</p>
+
+<p>
+Returns nonzero iff <em>dt</em>&rarr;fd is to be selected for reading.
+Should be called in every iteration.
+</p>
+
+<p>
+<code> int s6dns_engine_iswritable (s6dns_engine_t const *dt) </code>
+</p>
+
+<p>
+Returns nonzero iff <em>dt</em>&rarr;fd is to be selected for writing.
+Should be called in every iteration.
+</p>
+
+<h3> After the <tt>iopause()</tt> </h3>
+
+<p>
+<code> int s6dns_engine_timeout (s6dns_engine_t *dt, struct taia const *stamp) </code>
+</p>
+
+<p>
+This function should be called if your selecting function returned 0, which
+means that an event timed out.
+<em>stamp</em> should contain the current time. The function returns -1 if
+an error occurred, 1 if <em>dt</em> actually timed out, and 0 if nothing
+special happened to <em>dt</em> (and your iopause timeout was caused by
+something else). If the return value is not 0, <em>dt</em> is automatically
+recycled.
+</p>
+
+<p>
+<code> int s6dns_engine_event (s6dns_engine_t *dt, struct taia const *stamp) </code>
+</p>
+
+<p>
+This function should be called if your selecting function returned a positive
+number, which means that some event got triggered.
+<em>stamp</em> should contain the current time. The function returns
+-1 if an error occurred (and <em>dt</em> is automatically recycled). It
+returns 0 if no answer has arrived yet, and 1 if an answer is available.
+</p>
+
+<p>
+The <tt>s6dns_engine_timeout()</tt> and <tt>s6dns_engine_event()</tt> functions,
+when returning -1, make use of the following error codes:
+</p>
+<ul>
+ <li> EINVAL: Invalid <em>dt</em>.
+ <li> ENETUNREACH: All the servers in the <em>servers</em> list have been
+unsuccessfully tried. </li>
+ <li> EPROTO: An answer arrived, but it didn't follow the DNS protocol. </li>
+ <li> Other error codes reporting socket or system failures. </li>
+</ul>
+
+<p>
+<code> char *s6dns_engine_packet (s6dns_engine_t const *dt) </code>
+</p>
+
+<p>
+Points to the response packet received from the network,
+if <tt>s6dns_engine_event()</tt> returned 1.
+</p>
+
+<p>
+<code> unsigned int s6dns_engine_packetlen (s6dns_engine_t const *dt) </code>
+</p>
+
+<p>
+Gives the length of the response packet,
+if <tt>s6dns_engine_event()</tt> returned 1.
+</p>
+
+<p>
+ You should recycle or free <em>dt</em> after reading the response packet.
+</p>
+
+</body>
+</html>
diff --git a/doc/libs6dns/s6dns-fmt.html b/doc/libs6dns/s6dns-fmt.html
new file mode 100644
index 0000000..a586846
--- /dev/null
+++ b/doc/libs6dns/s6dns-fmt.html
@@ -0,0 +1,92 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6dns_fmt library interface</title>
+ <meta name="Description" content="s6-dns: the s6dns_fmt library interface" />
+ <meta name="Keywords" content="s6-dns dns s6dns_fmt library libs6dns" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">libs6dns</a><br />
+<a href="../">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6dns_fmt</tt> library interface </h1>
+
+<p>
+ The following functions and structures are declared in the <tt>s6-dns/s6dns-fmt.h</tt> header,
+and implemented in the <tt>libs6dns.a</tt> or <tt>libs6dns.so</tt> library.
+</p>
+
+<h2> General information </h2>
+
+<p>
+ <tt>s6dns_fmt</tt> provides primitives to format RR contents into
+printable strings.
+</p>
+
+<h2> Functions </h2>
+
+<p>
+<code> unsigned int s6dns_fmt_domain (char *s, unsigned int max, s6dns_domain_t const *d) </code> <br />
+Writes into string <em>s</em> the domain contained in *<em>d</em> (in string
+form). Returns the number of bytes written, or 0 in case of failure. If
+the output would be more than <em>max</em> bytes, 0 ENAMETOOLONG is returned.
+To avoid that, S6DNS_FMT_DOMAIN is a suitable number of bytes to preallocate <em>s</em>.
+This function is actually an alias to <tt>s6dns_domain_tostring</tt>.
+</p>
+
+<p>
+<code> unsigned int s6dns_fmt_domainlist (char *s, unsigned int max, s6dns_domain_t const *list, unsigned int n, char const *delin, unsigned int delimlen) </code> <br />
+Writes into string <em>s</em> the list of <em>n</em> domains (in string form)
+pointed to by <em>list</em>. Between each domain (and not after the last one),
+string <em>delim</em> of length <em>delimlen</em> is appended.
+The function returns the number of bytes written, or 0 in case of failure. If
+the output would be more than <em>max</em> bytes, 0 ENAMETOOLONG is returned.
+To avoid that, S6DNS_FMT_DOMAINLIST(n) is a suitable number of bytes to preallocate <em>s</em>.
+</p>
+
+<p>
+<code> unsigned int s6dns_fmt_hinfo (char *s, unsigned int max, s6dns_message_rr_hinfo_t const *p) </code> <br />
+Writes into string <em>s</em> the HINFO contained in *<em>p</em>: cpu, then os,
+separated by a space.
+Returns the number of bytes written, or 0 in case of failure. If
+the output would be more than <em>max</em> bytes, 0 ENAMETOOLONG is returned.
+To avoid that, S6DNS_FMT_HINFO is a suitable number of bytes to preallocate <em>s</em>.
+</p>
+
+<p>
+<code> unsigned int s6dns_fmt_mx (char *s, unsigned int max, s6dns_message_rr_mx_t const *p) </code> <br />
+Writes into string <em>s</em> the MX contained in *<em>p</em>: preference, then
+exchanger name, separated by a space.
+Returns the number of bytes written, or 0 in case of failure. If
+the output would be more than <em>max</em> bytes, 0 ENAMETOOLONG is returned.
+To avoid that, S6DNS_FMT_MX is a suitable number of bytes to preallocate <em>s</em>.
+</p>
+
+<p>
+<code> unsigned int s6dns_fmt_soa (char *s, unsigned int max, s6dns_message_rr_soa_t const *p) </code> <br />
+Writes into string <em>s</em> the SOA contained in *<em>p</em>:
+mname, then rname, then serial number, refresh time, retry time, expiration time
+and minimum time, separated by spaces.
+Returns the number of bytes written, or 0 in case of failure. If
+the output would be more than <em>max</em> bytes, 0 ENAMETOOLONG is returned.
+To avoid that, S6DNS_FMT_SOA is a suitable number of bytes to preallocate <em>s</em>.
+</p>
+
+<p>
+<code> unsigned int s6dns_fmt_srv (char *s, unsigned int max, s6dns_message_rr_srv_t const *p) </code> <br />
+Writes into string <em>s</em> the SRV contained in *<em>p</em>: priority,
+then weight, then port, then target, separated by spaces.
+Returns the number of bytes written, or 0 in case of failure. If
+the output would be more than <em>max</em> bytes, 0 ENAMETOOLONG is returned.
+To avoid that, S6DNS_FMT_SRV is a suitable number of bytes to preallocate <em>s</em>.
+</p>
+
+</body>
+</html>
diff --git a/doc/libs6dns/s6dns-ip46.html b/doc/libs6dns/s6dns-ip46.html
new file mode 100644
index 0000000..27cbe2c
--- /dev/null
+++ b/doc/libs6dns/s6dns-ip46.html
@@ -0,0 +1,69 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6dns_ip46 library interface</title>
+ <meta name="Description" content="s6-dns: the s6dns_ip46 library interface" />
+ <meta name="Keywords" content="s6-dns dns s6dns_ip46 library libs6dns" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">libs6dns</a><br />
+<a href="../">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6dns_ip46</tt> library interface </h1>
+
+<p>
+ The following functions are declared in the <tt>s6-dns/s6dns-ip46.h</tt> header,
+and implemented as macros.
+</p>
+
+<h2> General information </h2>
+
+<p>
+ <tt>s6dns_ip46</tt> is the transport abstraction layer. It allows
+the functions declared in <a href="s6dns-engine.html">s6-dns/s6dns-engine.h</a>
+and <a href="s6dns-rci.html">s6-dns/s6dns-rci.h</a> to be transport-agnostic,
+i.e. to be able to work with both IPv4 and IPv6.
+</p>
+
+<p>
+ If the underlying <a href="http://skarnet.org/software/skalibs/">skalibs</a>
+has been compiled with
+ <a href="http://skarnet.org/software/skalibs/flags.html#noipv6">flag-noipv6</a>,
+or if it has detected at build time that the target host does not support
+IPv6, then the s6dns-ip46 abstraction will be totally transparent and use
+no resources at all.
+</p>
+
+<h2> Data structures </h2>
+
+<p>
+ A <tt>s6dns_ip46list_t</tt> structure holds a list of S6DNS_MAX_SERVERS (16)
+IPv4 <em>or</em> IPv6 addresses. Such a mixed list can be constructed, for
+instance, if the <tt>/etc/resolv.conf</tt> file contains both v4 and v6
+<tt>nameserver</tt> lines.
+</p>
+
+<h2> Functions </h2>
+
+<p>
+ If <em>list</em> is a <tt>s6dns_ip46list_t</tt> and <em>i</em> an integer
+between 0 and DNS_MAX_SERVERS-1, then
+</p>
+
+<ul>
+ <li> <code>s6dns_ip46list_is6(&amp;list, i)</code> is 1 if the <em>i</em>th
+address in <em>list</em> is IPv6 and 0 if it is IPv4. </li>
+ <li> <code>s6dns_ip46list_ip(&amp;list, i)</code> is a <tt>char *</tt> pointer to
+16 (if IPv6) or 4 (IPv4) bytes representing the <em>i</em>th address in
+<em>list</em>, in network byte order. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/libs6dns/s6dns-message.html b/doc/libs6dns/s6dns-message.html
new file mode 100644
index 0000000..e06aa91
--- /dev/null
+++ b/doc/libs6dns/s6dns-message.html
@@ -0,0 +1,261 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6dns_message library interface</title>
+ <meta name="Description" content="s6-dns: the s6dns_message library interface" />
+ <meta name="Keywords" content="s6-dns dns s6dns_message library libs6dns" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">libs6dns</a><br />
+<a href="../">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6dns_message</tt> library interface </h1>
+
+<p>
+ The following functions are declared in the <tt>s6-dns/s6dns-message.h</tt> header,
+and implemented in the <tt>libs6dns.a</tt> or <tt>libs6dns.so</tt> library.
+</p>
+
+<h2> General information </h2>
+
+<p>
+ <tt>s6dns_message</tt> provides functions to read and parse DNS messages
+sent by servers and caches and containing answers to queries.
+</p>
+
+<h2> Data structures </h2>
+
+<p>
+ A <tt>s6dns_message_header_t</tt> is a structure containing the header
+of a received DNS packet, broken down for easy access to the bits.
+</p>
+
+<p>
+ A <tt>s6dns_message_rr_t</tt> is a structure containing the information
+about a resource record given by an answer packet - all of it, except the
+value of the answer itself, which is rtype-specific and has to be decoded
+by rtype-specific functions.
+</p>
+
+<p>
+ A <tt>s6dns_message_rr_func_t</tt> is the type of such a function. The
+prototype is <br />
+<code> int f (s6dns_message_rr_t const *rr, char const *packet, unsigned int packetlen, unsigned int pos, unsigned int section, void *data) </code> <br />
+</p>
+
+<ul>
+ <li> *<em>rr</em> contains the header information of the resource record,
+including the domain to which it applies, the rtype, and the ttl. </li>
+ <li> <em>packet</em> points to the <strong>whole DNS packet</strong> the RR
+is a part of. </li>
+ <li> <em>packetlen</em> is the total length of said packet. </li>
+ <li> <em>pos</em> is the offset of the RR in the DNS packet, i.e. <em>f</em>
+should start parsing at <em>packet + pos</em>. </li>
+ <li> <em>section</em> is the section the RR belongs to: 2 for answer, 3 for
+authority, 4 for additional. </li>
+ <li> <em>data</em> is a user-specified pointer; it is typically used to
+write the decoded output, in a rtype-specific way. </li>
+ <li> <em>f</em> must return 1 or more if it succeeds in decoding the packet.
+Else it must set errno appropriately, and return -1 if the error is local,
+unrelated to the packet - for instance, it has run out of memory - or 0 if it
+cannot decode the RR because of the packet contents. </li>
+</ul>
+
+<p>
+ Various structures designed to store specific resource record types are
+also provided. The list includes:
+</p>
+
+<ul>
+ <li> <tt>s6dns_message_rr_hinfo</tt> for HINFO RRs </li>
+ <li> <tt>s6dns_message_rr_mx</tt> for MX RRs </li>
+ <li> <tt>s6dns_message_rr_soa</tt> for SOA RRs </li>
+ <li> <tt>s6dns_message_rr_srv</tt> for SRV RRs </li>
+</ul>
+
+<h2> Functions </h2>
+
+<h3> Header management </h3>
+
+<p>
+<code> void s6dns_message_header_pack (char *s, s6dns_message_header_t const *h) </code> <br />
+Packs the header *<em>h</em> into the 12 bytes pointed to by <em>s</em>.
+</p>
+
+<p>
+<code> void s6dns_message_header_unpack (char const *s, s6dns_message_header_t *h) </code> <br />
+Unpacks the 12 bytes pointed to by <em>s</em> into the structure *<em>h</em>.
+</p>
+
+<h3> Low-level RR decoding </h3>
+
+<p>
+ The following primitives are used in the implementation of
+<tt>s6dns_message_rr_func_t</tt>-typed functions, to read and decode information
+stored in a DNS packet. Their arguments are:
+</p>
+
+<ol>
+ <li> A pointer to the structure where the information is going to be stored </li>
+ <li> A read-only pointer to the beginning of the DNS packet </li>
+ <li> The length of the DNS packet </li>
+ <li> A pointer to an integer containing the current position in the
+DNS packet, i.e. where the information is going to be read. If the functions
+succeed, they automatically update the position so information can be read
+sequentially. </li>
+</ol>
+
+<p>
+<code> int s6dns_message_get_string (s6dns_domain_t *d, char const *packet, unsigned int packetlen, unsigned int *pos) </code> <br />
+Reads a character-string and stores it into *<em>d</em>. Returns 1 on success
+and 0 on failure. Note that *<em>d</em> does not contain a domain, but the
+<tt>s6dns_domain_t</tt> structure is adapted to store strings that do not
+exceed 255 characters. <em>d</em>&rarr;s can be used to access the string,
+and <em>d</em>&rarr;len its length.
+</p>
+
+<p>
+<code> int s6dns_message_get_strings (char *s, unsigned int rdlength, char const *packet, unsigned int packetlen, unsigned int *pos </code> <br />
+This function takes an additional parameter <em>rdlength</em>. It reads a series of
+character-strings and stores their concatenation into the string <em>s</em>, which
+must be preallocated; it can never store more than <em>rdlength</em> bytes. It returns
+-1 if it fails; on success, it returns the number of bytes written. The
+<em>rdlength</em> parameter must be the length of the resource record containing
+the series of character-strings.
+</p>
+
+<p>
+<code> unsigned int s6dns_message_get_domain (s6dns_domain_t *d, char const *packet, unsigned int packetlen, unsigned int *pos) </code> <br />
+Reads a domain and stores it, in string form, into *<em>d</em>.
+Returns 1 on success and 0 on failure, and sets errno:
+</p>
+
+<ul>
+ <li> EPROTO: there is no proper domain to be read in position *<em>pos</em> of
+<em>packet</em>. Either the packet is malformed or the function is being
+misused. </li>
+ <li> EPROTONOSUPPORT: the domain encoding uses an extension that the function
+does not recognize. </li>
+</ul>
+
+<p>
+<code> int s6dns_message_get_hinfo (s6dns_message_rr_hinfo_t *p, char const *packet, unsigned int packetlen, unsigned int *pos) </code> <br />
+Reads a HINFO RR and stores it into *<em>p</em>. Returns 1 on success or 0
+on failure.
+</p>
+
+<p>
+<code> int s6dns_message_get_mx (s6dns_message_rr_mx_t *p, char const *packet, unsigned int packetlen, unsigned int *pos) </code> <br />
+Reads a MX RR and stores it into *<em>p</em>. Returns 1 on success or 0 on failure.
+</p>
+
+<p>
+<code> int s6dns_message_get_soa (s6dns_message_rr_soa_t *p, char const *packet, unsigned int packetlen, unsigned int *pos) </code> <br />
+Reads a SOA RR and stores it into *<em>p</em>. Returns 1 on success or 0 on failure.
+</p>
+
+<p>
+<code> int s6dns_message_get_srv (s6dns_message_rr_srv_t *p, char const *packet, unsigned int packetlen, unsigned int *pos) </code> <br />
+Reads a SRV RR and stores it into *<em>p</em>. Returns 1 on success or 0 on failure.
+</p>
+
+<h3> High-level RR-specific parsing functions </h3>
+
+<p>
+<code> s6dns_message_func_t s6dns_message_parse_answer_strings </code> <br />
+Parses character-strings located in the answer section of the packet. The
+<em>data</em> argument is interpreted as a pointer to a <tt>s6dns_mpag_t</tt>,
+which is a structure defined in the <tt>s6-dns/s6dns-message.h</tt> header
+and used to store multiple character-strings.
+</p>
+
+<p>
+<code> s6dns_message_func_t s6dns_message_parse_answer_domain </code> <br />
+Parses domains located in the answer section of the packet. The
+<em>data</em> argument is interpreted as a pointer to a <tt>s6dns_dpag_t</tt>,
+which is a structure defined in the <tt>s6-dns/s6dns-message.h</tt> header
+and used to store multiple domains.
+</p>
+
+<p>
+<code> s6dns_message_func_t s6dns_message_parse_answer_a </code> <br />
+Parses A RRs located in the answer section of the packet. The
+<em>data</em> argument is interpreted as a pointer to a
+<a href="http://skarnet.org/software/skalibs/libstddjb/stralloc.html">stralloc</a>,
+and 4 bytes are appended to this stralloc for every IPv4 address found.
+</p>
+
+
+<code> s6dns_message_func_t s6dns_message_parse_answer_aaaa </code> <br />
+Parses AAAA RRs located in the answer section of the packet. The
+<em>data</em> argument is interpreted as a pointer to a
+<a href="http://skarnet.org/software/skalibs/libstddjb/stralloc.html">stralloc</a>,
+and 16 bytes are appended to this stralloc for every IPv6 address found.
+</p>
+
+<p>
+<code> s6dns_message_func_t s6dns_message_parse_answer_hinfo </code> <br />
+Parses HINFO RRs located in the answer section of the packet. The
+<em>data</em> argument is interpreted as a pointer to a
+<a href="http://skarnet.org/software/skalibs/libstddjb/genalloc.html">genalloc</a>
+containing <tt>s6dns_message_rr_hinfo_t</tt> structures.
+</p>
+
+<p>
+<code> s6dns_message_func_t s6dns_message_parse_answer_mx </code> <br />
+Parses MX RRs located in the answer section of the packet. The
+<em>data</em> argument is interpreted as a pointer to a
+<a href="http://skarnet.org/software/skalibs/libstddjb/genalloc.html">genalloc</a>
+containing <tt>s6dns_message_rr_mx_t</tt> structures.
+</p>
+
+<p>
+<code> s6dns_message_func_t s6dns_message_parse_answer_soa </code> <br />
+Parses SOA RRs located in the answer section of the packet. The
+<em>data</em> argument is interpreted as a pointer to a
+<a href="http://skarnet.org/software/skalibs/libstddjb/genalloc.html">genalloc</a>
+containing <tt>s6dns_message_rr_soa_t</tt> structures.
+</p>
+
+<p>
+<code> s6dns_message_func_t s6dns_message_parse_answer_srv </code> <br />
+Parses SRV RRs located in the answer section of the packet. The
+<em>data</em> argument is interpreted as a pointer to a
+<a href="http://skarnet.org/software/skalibs/libstddjb/genalloc.html">genalloc</a>
+containing <tt>s6dns_message_rr_srv_t</tt> structures.
+</p>
+
+<h3> High-level packet parsing </h3>
+
+<p>
+<code> int s6dns_message_parse (s6dns_message_header_t *h, char const *packet, unsigned int packetlen, s6dns_message_rr_func_t *f, void *data) </code> <br />
+ This function parses the DNS packet <em>packet</em> of length <em>packetlen</em>.
+It stores the packet header into *<em>h</em>. Then, for every RR in the answer,
+authority or additional section of the packet, it calls <em>f</em> with the
+relevant parameters. <em>data</em> is the extra pointer given to <em>f</em> to
+store information. The function returns 1 if the parsing succeeds. Otherwise it
+returns -1 if there is a local error unrelated to the packet, or 0 if no
+appropriate answer can be decoded from the packet. errno then contains one
+of the following values:
+</p>
+
+<ul>
+ <li> EPROTO: the packet is malformed, syntax error </li>
+ <li> EBADMSG: rcode 1, query format error </li>
+ <li> EBUSY: rcode 2, server failure </li>
+ <li> ENOENT: rcode 3, nxdomain </li>
+ <li> ENOTSUP: rcode 4, query type not implemented </li>
+ <li> ECONNREFUSED: rcode 5, operation refused by server </li>
+ <li> EIO: unknown rcode </li>
+ <li> any other value being set by <em>f</em> if it returns a failure code. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/libs6dns/s6dns-rci.html b/doc/libs6dns/s6dns-rci.html
new file mode 100644
index 0000000..1cf8a0e
--- /dev/null
+++ b/doc/libs6dns/s6dns-rci.html
@@ -0,0 +1,109 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6dns_rci library interface</title>
+ <meta name="Description" content="s6-dns: the s6dns_rci library interface" />
+ <meta name="Keywords" content="s6-dns dns s6dns_rci resolv.conf library libs6dns" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">libs6dns</a><br />
+<a href="../">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6dns_rci</tt> library interface </h1>
+
+<p>
+ The following functions and structures are declared in the <tt>s6-dns/s6dns-rci.h</tt> header,
+and implemented in the <tt>libs6dns.a</tt> or <tt>libs6dns.so</tt> library.
+</p>
+
+<h2> General information </h2>
+
+<p>
+ <tt>s6dns_rci</tt> provides functions to get information from
+the <tt>/etc/resolv.conf</tt> file.
+</p>
+
+<h2> Data structures </h2>
+
+<p>
+ A <tt>s6dns_rci_t</tt> is a structure storing information
+provided by the <tt>/etc/resolv.conf</tt> file, i.e.
+</p>
+
+<ul>
+ <li> The list of "nameserver" addresses, i.e. IP addresses of DNS caches
+contacted by clients for recursive queries </li>
+ <li> The list of qualification rules provided by "domain" or "search"
+lines </li>
+</ul>
+
+<p>
+ Nameserver addresses are stored in a
+<a href="s6dns-ip46.html">s6dns_ip46list_t</a>. Qualification rules are
+stored in a <a href="http://skarnet.org/software/skalibs/libstddjb/stralloc.html">stralloc</a>
+with an additional integer storing the number of rules.
+</p>
+
+<p>
+ Most programs won't need more than one <tt>s6dns_rci_t</tt>, so
+the library provides the global variable <tt>s6dns_rci_here</tt>, used
+by default in simple resolution macros.
+</p>
+
+<h2> Functions </h2>
+
+<p>
+<code> int s6dns_rci_init (s6dns_rci_t *rci, char const *file) </code> <br />
+Extracts information from <em>file</em>, which must be in <tt>/etc/resolv.conf</tt>
+format, and stores it into *<em>rci</em>. <em>rci</em> must be previously
+initialized to the <tt>S6DNS_RCI_ZERO</tt> constant. The function returns 1 if
+it succeeds, or 0 (and sets errno) if it fails.
+</p>
+
+<p>
+ If the DNSCACHEIP environment variable is set, and contains a list of
+IP addresses separated by commas, semicolons, spaces, tabs, newlines or
+carriage returns, then this list overrides any nameserver information
+from <em>file</em>. If the variable is empty, <em>file</em> will be used
+as the source of the information.
+</p>
+
+<p>
+ If the DNSQUALIFY environment variable is set, a list of domain suffixes,
+separated by spaces, tabs, newlines or carriage returns, is read from it,
+and overrides any qualification information from <em>file</em>. If the
+variable is empty, it amounts to one rule saying "no qualification".
+</p>
+
+<p>
+ <tt>s6dns_init()</tt> is an alias to
+<tt>s6dns_rci_init(&amp;s6dns_rci_here, "/etc/resolv.conf")</tt>.
+</p>
+
+<p>
+<code> void s6dns_rci_free (s6dns_rci_t *rci) </code> <br />
+Frees the memory used by *<em>rci</em>. <em>rci</em> is then suitable to
+be reused in a <tt>s6dns_rci_init</tt> call.
+</p>
+
+<p>
+ <tt>s6dns_finish()</tt> calls <tt>s6dns_rci_free(&amp;s6dns_rci_here)</tt>.
+</p>
+
+<p>
+<code> unsigned int s6dns_qualify (s6dns_domain_t *list, s6dns_domain_t const *d) </code> <br />
+Qualifies domain *<em>d</em> into the list of domains pointed to by <em>list</em>
+according to the rules stored in <tt>s6dns_rci_here</tt>. Returns the number of
+written domains (0 if it fails); this number cannot exceed
+<tt>s6dns_rci_here.rulesnum</tt>.
+</p>
+
+</body>
+</html>
diff --git a/doc/libs6dns/s6dns-resolve.html b/doc/libs6dns/s6dns-resolve.html
new file mode 100644
index 0000000..bbb3f57
--- /dev/null
+++ b/doc/libs6dns/s6dns-resolve.html
@@ -0,0 +1,387 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6dns_resolve library interface</title>
+ <meta name="Description" content="s6-dns: the s6dns_resolve library interface" />
+ <meta name="Keywords" content="s6-dns dns s6dns_resolve library libs6dns" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">libs6dns</a><br />
+<a href="../">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6dns_resolve</tt> library interface </h1>
+
+<p>
+ The following functions are declared in the <tt>s6-dns/s6dns-resolve.h</tt> header,
+and implemented in the <tt>libs6dns.a</tt> or <tt>libs6dns.so</tt> library.
+</p>
+
+<h2> General information </h2>
+
+<p>
+ <tt>s6dns_resolve</tt> provides functions and macros - mostly macros -
+to perform high level synchronous DNS resolution.
+</p>
+
+<p>
+ All the functions declared here make synchronous calls to the network, so
+they can block for a non-negligible amount of time. To avoid unbounded
+waiting times, they always take 2 arguments at the end, <em>deadline</em>
+and <em>stamp</em>. <em>deadline</em> is the read-only address of a
+<a href="http://skarnet.org/software/skalibs/libstddjb/tai.h">struct taia</a>
+containing an absolute time which is the deadline for the function, and
+<em>stamp</em> is the read-write address of a <tt>struct taia</tt> being
+an accurate enough representation of the current absolute time. If
+the function has not returned by *<em>deadline</em>, then it immediately
+returns with a failure code and errno set to ETIMEDOUT. In every case,
+*<em>stamp</em> is automatically updated so it always represents the
+absolute time accurately enough.
+</p>
+
+<p>
+ In a single-threaded program, the STAMP global variable can be used to
+store the current time. Macros ending with <tt>_g</tt> use this variable
+automatically so you don't have to provide the <em>stamp</em> argument
+everytime. Additionally, several resolution functions make implicit use
+of global variables such as:
+</p>
+
+<ul>
+ <li> <tt>s6dns_engine_here</tt>: a global
+<a href="s6dns-engine.html">s6dns_engine_t</a> storing the current
+query, for sequential queries. </li>
+ <li> <tt>s6dns_debughook_zero</tt>: a global <tt>s6dns_debughook_t</tt>
+meaning no debugging is needed. </li>
+ <li> <tt>s6dns_rci_here</tt>: a global
+<a href="s6dns-rci.html">s6dns_rci_t</a> containing the current
+resolv.conf information. </li>
+</ul>
+
+<p>
+ Reentrant, non-global-using functions are also provided, with the <tt>_r</tt>
+suffix. In other words, if <em>foobarfunc</em> is a resolution function,
+the following prototypes are generally provided, from the simplest to the
+most complex:
+</p>
+
+<ul>
+ <li> <em>foobarfunc_g</em>: only the relevant parameters and a deadline
+must be given, the function uses the STAMP global and maybe DNS-specific
+globals </li>
+ <li> <em>foobarfunc</em>: a deadline and stamp must be given, the function
+uses all the DNS-specific globals </li>
+ <li> <em>foobarfunc_r_g</em>: No DNS-specific globals are used, the
+information must be given via parameters, but the STAMP global is used </li>
+ <li> <em>foobarfunc_r</em>: fully reentrant function using no globals
+at all </li>
+</ul>
+
+<p>
+ For each set of four functions, only one is documented here.
+The other prototypes can be found in the <tt>s6-dns/s6dns-resolve.h</tt> file.
+</p>
+
+<p>
+ Some <tt>errno</tt> codes reported by these functions do not have
+exactly the system meaning given by
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/strerror.html">strerror()</a>.
+To get a user-friendly error message, use
+<tt>s6dns_constants_error_str(errno)</tt> instead. The <tt>s6dns_constants_error_str</tt>
+function is declared in the <tt>s6dns-constants.h</tt> header.
+</p>
+
+<h2> Functions </h2>
+
+<h3> Single query resolution </h3>
+
+<p>
+ These functions are ordered from the lowest level to the highest level.
+</p>
+
+<h4> Basic wrapper around s6dns_engine </h4>
+
+<p>
+<code> int s6dns_resolve_loop_r_g (s6dns_engine_t *dt, struct taia const *deadline) </code> <br />
+Resolves the query stored in <tt>dt</tt>.
+Returns 1 on success or 0 on failure.
+</p>
+
+<h4> Generic resolution functions </h4>
+
+<p>
+<code> int s6dns_resolve_core_g (s6dns_domain_t const *d, uint16 qtype, struct taia const *deadline) </code> <br />
+Resolves the query on domain *<em>d</em> (in packet form), of type <em>qtype</em>.
+Returns 0 on failure, or 1 on success, in which case
+<tt>s6dns_engine_here</tt> contains the answer.
+</p>
+
+<p>
+<code> int s6dns_resolve_parse_g (s6dns_domain_t const *d, uint16 qtype, s6dns_message_rr_func_t *f, void *data, struct taia const *deadline) </code> <br />
+Resolves the query on domain *<em>d</em> (in packet form), of type <em>qtype</em>,
+then parses the answer with function <em>f</em> and stores the result into <em>data</em>.
+Returns 1 if it succeeds, 0 if no data can be extracted from the answer, or -1 if an
+error occurs. Sets errno in the last two cases.
+</p>
+
+<p>
+ Note that the function can return 1 without appending anything to <em>data</em>.
+This means that the servers confirmed that the domain exists, but <em>f</em>
+has not been able to find any data relevant to the query in the answer.
+This is very different from NXDOMAIN, which means that
+the servers deny the actual existence of the domain, and which is reported
+here as a return code of 0 with errno set to ENOENT.
+</p>
+
+<p>
+<code> int s6dns_resolvenoq_g (char const *name, unsigned int len, uint16 qtype, s6dns_message_rr_func_t *f, void *data, struct taia const *deadline) </code> <br />
+Performs a query of type <em>qtype</em> on name <em>name</em> of length <em>len</em>,
+without qualifying it. Parses the answer with function <em>f</em> and stores the
+result into <em>data</em>. Returns 1 if it succeeds, 0 if no data can be extracted,
+or -1 if an error occurs. Sets errno in the last two cases.
+</p>
+
+<p>
+<code> int s6dns_resolveq_g (char const *name, unsigned int len, uint16 qtype, s6dns_message_rr_func_t *f, void *data, struct taia const *deadline) </code> <br />
+Performs a query of type <em>qtype</em> on name <em>name</em> of length <em>len</em>,
+qualifying it first. Parses the answer with function <em>f</em> and stores the
+result into <em>data</em>. Returns 1 if it succeeds, 0 if none of the FQDNs can
+get a positive answer, or -1 if an error occurs. Sets errno in the last two cases.
+</p>
+
+<p>
+<code> int s6dns_resolve_g (char const *name, unsigned int len, uint16 qtype, s6dns_message_rr_func_t *f, void *data, int qualif, struct taia const *deadline) </code> <br />
+Performs a query of type <em>qtype</em> on name <em>name</em> of length <em>len</em>.
+Qualifies <em>name</em> first if <em>qualif</em> is nonzero; else, does not
+qualify it. Parses the answer with function <em>f</em> and stores the
+result into <em>data</em>. Returns 1 if it succeeds, 0 if none of the FQDNs can
+get a positive answer, or -1 if an error occurs. Sets errno in the last two cases.
+</p>
+
+<h4> High-level type-specific functions </h4>
+
+<p>
+<code> int s6dns_resolve_a_g (stralloc *ips, char const *name, unsigned int len, int qualif, struct taia const *deadline) </code> <br />
+Performs an A query on name <em>name</em> of length <em>len</em>, qualifying it
+iff <em>qualif</em> is nonzero. Returns -1 if an error occurs, or 0 if no answer
+can be obtained from servers, or 1 if it succeeds, in which case the IPs are
+appended to the stralloc *<em>ips</em>, using 4 bytes per answer.
+</p>
+
+<p>
+<code> int s6dns_resolve_aaaa_g (stralloc *ips, char const *name, unsigned int len, int qualif, struct taia const *deadline) </code> <br />
+Performs an AAAA query on name <em>name</em> of length <em>len</em>, qualifying it
+iff <em>qualif</em> is nonzero. Returns -1 if an error occurs, or 0 if no answer
+can be obtained from servers, or 1 if it succeeds, in which case the IPs are
+appended to the stralloc *<em>ips</em>, using 16 bytes per answer.
+</p>
+
+<p>
+<code> int s6dns_resolve_aaaaa_g (genalloc *ips, char const *name, unsigned int len, int qualif, struct taia const *deadline) </code> <br />
+Performs an AAAA query and an A query at the same time on name <em>name</em>
+of length <em>len</em>, qualifying it first iff <em>qualif</em> is nonzero.
+Returns -1 if an error occurs, or 0 if no answer
+can be obtained from servers, or a positive number if it succeeds: 1 if IPv4 addresses
+were found, 2 if IPv6 addresses were found, and 3 if both were found.
+The IPs are appended to the genalloc *<em>ips</em>, which contains an array of
+<tt>ip46_t</tt>, the skalibs structure used to store IPv4 and IPv6 addresses
+indiscriminately.
+</p>
+
+<p>
+<code> int s6dns_resolve_ptr_g (genalloc *ds, char const *name, unsigned int len, int qualif, struct taia const *deadline) </code> <br />
+Performs a PTR query on name <em>name</em> of length <em>len</em>, qualifying it
+iff <em>qualif</em> is nonzero. Returns -1 if an error occurs, or 0 if no answer
+can be obtained from servers, or 1 if it succeeds, in which case the domains are
+appended to the genalloc *<em>ds</em>, which contains an array of <tt>s6dns_domain_t</tt>.
+</p>
+
+<p>
+<code> int s6dns_resolve_name4_g (genalloc *ds, char const *ip, struct taia const *deadline) </code> <br />
+Performs a PTR query on the <tt>in-addr.arpa.</tt> name representing the IPv4
+address <em>ip</em> (4 network-order bytes).
+Returns -1 if an error occurs, or 0 if no answer
+can be obtained from servers, or 1 if it succeeds, in which case the domains are
+appended to the genalloc *<em>ds</em>, which contains an array of <tt>s6dns_domain_t</tt>.
+</p>
+
+<p>
+<code> int s6dns_resolve_name6_g (genalloc *ds, char const *ip, struct taia const *deadline) </code> <br />
+Performs a PTR query on the <tt>ip6.arpa.</tt> name representing the IPv6
+address <em>ip</em> (16 network-order bytes).
+Returns -1 if an error occurs, or 0 if no answer
+can be obtained from servers, or 1 if it succeeds, in which case the domains are
+appended to the genalloc *<em>ds</em>, which contains an array of <tt>s6dns_domain_t</tt>.
+</p>
+
+<p>
+<code> int s6dns_resolve_name46_g (genalloc *ds, ip46_t const *ip, struct taia const *deadline) </code> <br />
+Calls <tt>s6dns_resolve_name6_g()</tt> or <tt>s6dns_resolve_name4_g()</tt>
+depending on which <em>ip</em> is an IPv6 or IPv4 address.
+</p>
+
+<p>
+<code> int s6dns_resolve_ns_g (genalloc *ds, char const *name, unsigned int len, int qualif, struct taia const *deadline) </code> <br />
+Performs a NS query on name <em>name</em> of length <em>len</em>, qualifying it
+iff <em>qualif</em> is nonzero. Returns -1 if an error occurs, or 0 if no answer
+can be obtained from servers, or 1 if it succeeds, in which case the domains are
+appended to the genalloc *<em>ds</em>, which contains an array of <tt>s6dns_domain_t</tt>.
+</p>
+
+<p>
+<code> int s6dns_resolve_cname_g (genalloc *ds, char const *name, unsigned int len, int qualif, struct taia const *deadline) </code> <br />
+Performs a CNAME query on name <em>name</em> of length <em>len</em>, qualifying it
+iff <em>qualif</em> is nonzero. Returns -1 if an error occurs, or 0 if no answer
+can be obtained from servers, or 1 if it succeeds, in which case the domains are
+appended to the genalloc *<em>ds</em>, which contains an array of <tt>s6dns_domain_t</tt>.
+</p>
+
+<p>
+<code> int s6dns_resolve_hinfo_g (genalloc *hinfos, char const *name,
+unsigned int len, int qualif, struct taia const *deadline) </code> <br />
+ Performs an HINFO query on name <em>name</em> of length <em>len</em>, qualifying it
+iff <em>qualif</em> is nonzero. Returns -1 if an error occurs, or 0 if no answer
+can be obtained from servers, or 1 if it succeeds, in which case the domains are
+appended to the genalloc *<em>hinfos</em>, which contains an array of
+<tt>s6dns_message_rr_hinfo_t</tt>.
+</p>
+
+<p>
+<code> int s6dns_resolve_mx_g (genalloc *mxs, char const *name,
+unsigned int len, int qualif, struct taia const *deadline) </code> <br />
+ Performs an MX query on name <em>name</em> of length <em>len</em>, qualifying it
+iff <em>qualif</em> is nonzero. Returns -1 if an error occurs, or 0 if no answer
+can be obtained from servers, or 1 if it succeeds, in which case the domains are
+appended to the genalloc *<em>mxs</em>, which contains an array of
+<tt>s6dns_message_rr_mx_t</tt>.
+</p>
+
+<p>
+<code> int s6dns_resolve_soa_g (genalloc *soas, char const *name,
+unsigned int len, int qualif, struct taia const *deadline) </code> <br />
+ Performs an SOA query on name <em>name</em> of length <em>len</em>, qualifying it
+iff <em>qualif</em> is nonzero. Returns -1 if an error occurs, or 0 if no answer
+can be obtained from servers, or 1 if it succeeds, in which case the domains are
+appended to the genalloc *<em>soas</em>, which contains an array of
+<tt>s6dns_message_rr_soa_t</tt>.
+</p>
+
+<p>
+<code> int s6dns_resolve_srv_g (genalloc *srvs, char const *name,
+unsigned int len, int qualif, struct taia const *deadline) </code> <br />
+ Performs an SRV query on name <em>name</em> of length <em>len</em>, qualifying it
+iff <em>qualif</em> is nonzero. Returns -1 if an error occurs, or 0 if no answer
+can be obtained from servers, or 1 if it succeeds, in which case the domains are
+appended to the genalloc *<em>srvs</em>, which contains an array of
+<tt>s6dns_message_rr_srv_t</tt>.
+</p>
+
+<p>
+<code> int s6dns_resolve_txt_g (stralloc *sa, genalloc *offsets, char const *name,
+unsigned int len, int qualif, struct taia const *deadline) </code> <br />
+ Performs an TXT query on name <em>name</em> of length <em>len</em>, qualifying it
+iff <em>qualif</em> is nonzero. Returns -1 if an error occurs, or 0 if no answer
+can be obtained from servers, or 1 if it succeeds, in which case:
+</p>
+
+<ul>
+ <li> The resulting strings are all stored into stralloc *<em>sa</em>.
+Every string is terminated by a null character. </li>
+ <li> A series of unsigned ints is appended to genalloc *<em>offsets</em>.
+Every integer represents the offset in *<em>sa</em> at which a string is stored.
+The number of appended integers is the total number of answers. </li>
+</ul>
+
+<h4> Parallel resolution </h4>
+
+<p>
+<code> int s6dns_resolven_loop_g (s6dns_engine_t *dtl, unsigned int n,
+unsigned int or, struct taia const *deadline) </code> <br />
+ Resolves the <em>n</em> queries stored in the array pointed to by <em>dtl</em>,
+in parallel. If <em>or</em> is zero, it does not return before all answers
+have arrived. If <em>or</em> is 1, it returns when an answer arrives, but does
+not return if a query generates an error (unless all queries do so). If
+<em>or</em> is 2, it returns when an answer arrives or an error occurs.
+Other values of <em>or</em> are unspecified yet.
+
+<p>
+ The return code is as follows:
+</p>
+
+<ul>
+ <li> If <em>or</em> is 0: -1 on a global error (i.e. not specific to
+a query), or a non-negative number which is the total of successful
+queries. </li>
+ <li> If <em>or</em> is 1 or 2: -1 on a global error, or a non-negative
+number which is the index of the query that triggered the event. </li>
+</ul>
+
+<p>
+ If <em>or</em> is 1, a return code of -1 with errno set to ENOENT
+means that all the queries failed.
+</p>
+
+<p>
+ After the function returns, the <tt>status</tt> field of each
+<tt>s6dns_engine_t</tt> contains the error code relative to the query.
+A status of 0 means that an answer has properly arrived; EAGAIN means
+that the query is still pending (and the s6dns_engine_t has not been
+recycled); ECONNABORTED means that the query has not been properly
+initialized. Other codes report various network problems.
+</p>
+
+<p>
+<code> int s6dns_resolven_parse_g (s6dns_resolve_t const *list, unsigned int n,
+struct taia const *deadline) </code> <br />
+Performs <em>n</em> complete resolutions in parallel, parsing the results.
+Returns 1 in case of success or 0 if a global error occurred.
+</p>
+
+<p>
+<em>list</em> is a pointer to an array of <em>n</em> <tt>s6dns_resolve_t</tt>,
+which is a structure containing at least the following fields:
+</p>
+
+<ul>
+ <li> <tt>q</tt>&nbsp;: a <tt>s6dns_domain_t</tt> containing the query.
+It must be in encoded form. </li>
+ <li> <tt>qtype</tt>&nbsp;: a <tt>uint16</tt> containing the query type.
+A list of valid query types can be found in the <tt>s6dns-constants.h</tt>
+header. </li>
+ <li> <tt>options</tt>&nbsp;: a <tt>uint32</tt> containing options passed
+to <a href="s6dns_engine.html">s6dns_engine_init()</a>. </li>
+ <li> <tt>deadline</tt>&nbsp;: a <tt>struct taia</tt> containing the
+deadline for this query, i.e. the query will fail with an ETIMEDOUT code
+if no answer has arrived by then. Note that the <em>deadline</em>
+argument given to s6dns_resolven_parse() is a global deadline that
+can make the function return with 0 ETIMEDOUT, but is independent from
+this field, which is local to every query. </li>
+ <li> <tt>parsefunc</tt>&nbsp;: a <tt>s6dns_message_rr_func_t</tt>
+function that will be used to parse the answer. </li>
+ <li> <tt>data</tt>&nbsp;: a <tt>void *</tt> that will be passed as an
+additional pointer to the <tt>parsefunc</tt> function; it is generally
+used to store the parsing result. </li>
+ <li> <tt>status</tt>&nbsp;: an <tt>int</tt>. It does not need to be
+initialized. It will contain the error code for the query after the
+function returns. ECONNABORTED means that the query could not be
+started at all; EAGAIN means that the query was still pending when
+an error happened; other codes report various network problems. </li>
+</ul>
+
+<p>
+ The s6dns_resolven_parse() function is a simple, convenient way to
+perform several resolutions in parallel to avoid the waiting time
+incurred by serial resolutions. However, it is still a synchronous
+function, and cannot replace a real asynchronous DNS library: for
+more complex parallel resolution needs, use the
+<a href="../skadns/">skadns</a> library.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-dnsip4-filter.html b/doc/s6-dnsip4-filter.html
new file mode 100644
index 0000000..5433438
--- /dev/null
+++ b/doc/s6-dnsip4-filter.html
@@ -0,0 +1,111 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6-dnsip4-filter program</title>
+ <meta name="Description" content="s6-dns: the s6-dnsip4-filter program" />
+ <meta name="Keywords" content="s6-dns client s6-dnsip4-filter dnsip domain name ip address ipv4 filter" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-dnsip4-filter program </h1>
+
+<p>
+ s6-dnsip4-filter reads domain names on its standard input and
+prints the corresponding IPv4 addresses on its standard output.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+ s6-dnsip4-filter [ -l <em>maxlines</em> ] [ -c <em>maxconn</em> ] [ -t <em>timeout</em> ] [ -f <em>normalfmt</em> ] [ -e <em>errorfmt</em> ]
+</pre>
+
+<ul>
+ <li> s6-dnsip4-filter reads lines from its stdin, processes them, and prints
+the processed lines to its stdout, in the same order. It exits 0 when it
+reads EOF on stdin. </li>
+ <li> Every line starting with a domain name is processed. Lines not
+starting with a domain name are not, and the formatting rules treat them
+as a pure "remainder". </li>
+ <li> For every processed line, an A query is made, and the result is
+printed according to the formatting rules. The domain is not qualified
+before being resolved. </li>
+ <li> By default, s6-dnsip4-filter looks for DNS cache addresses in the
+<tt>/etc/resolv.conf</tt> file. If the DNSCACHEIP environment variable is set
+and contains a list of IP (v4 or v6) addresses, separated by commas,
+semicolons, spaces, tabs, newlines or carriage returns, then this list
+is used instead. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-l</tt>&nbsp;<em>maxlines</em>&nbsp;: maximum lines. s6-dnsip4-filter
+will keep at most <em>maxlines</em> lines in memory at the same time. If line
+<em>n</em> is still waiting for resolution and cannot be printed, then
+s6-dnsip4-filter will stop reading from stdin before line <em>n+maxlines</em>,
+until the result arrives for line <em>n</em>. By default, <em>maxlines</em>
+is 256. </li>
+ <li> <tt>-c</tt>&nbsp;<em>maxconn</em>&nbsp;: maximum concurrent resolutions.
+s6-dnsip4-filter will perform at most <em>maxconn</em> resolutions in
+parallel at the same time. <em>maxlines</em> cannot be lesser than <em>maxconn</em>.
+By default, <em>maxconn</em> is 128. </li>
+ <li> <tt>-t</tt>&nbsp;<em>timeout</em>&nbsp;: if any resolution takes more
+than <em>timeout</em> milliseconds, then it is aborted and printed as a
+timeout error. By default, <em>timeout</em> is 0, which means no timeout. </li>
+ <li> <tt>-f</tt>&nbsp;<em>normalfmt</em>&nbsp;: print the positive results according to
+the <em>normalfmt</em> format string. By default, <em>normalfmt</em> is
+<tt>%s=%d%w%r</tt>, which means: print the original domain name, then an equal
+sign, then the corresponding IPv4 address, then the remainder of the line. </li>
+ <li> <tt>-e</tt>&nbsp;<em>errorfmt</em>&nbsp;: print the negative results according to
+the <em>errorfmt</em> format string. By default, <em>errorfmt</em> is
+<tt>%s=&lt;%e%&gt;%w%r</tt>, which means: print the original domain name, then an equal
+sign, then the error message between angle brackets, then the remainder of the line. </li>
+</ul>
+
+<h2> Formatting rules </h2>
+
+<p>
+ <em>normalfmt</em> and <em>errorfmt</em> are format strings, i.e. they tell the
+program how a line must be printed. The following sequences are recognized:
+</p>
+
+<ul>
+ <li> <tt>%%</tt> prints a single <tt>%</tt> character </li>
+ <li> <tt>%s</tt> prints the original domain name </li>
+ <li> <tt>%d</tt> prints the IP address, if any </li>
+ <li> <tt>%e</tt> prints the error message, if any </li>
+ <li> <tt>%w%r</tt> prints the remainder of the input line </li>
+</ul>
+
+<h2> Example </h2>
+
+<pre>
+ s6-dnsns google.com | s6-dnsip4-filter -f "%d"
+</pre>
+
+<p>
+ prints all the nameserver addresses for the <tt>google.com</tt>
+domain. This is useful, for instance, to give the result as an
+argument to <a href="s6-dnsq.html">s6-dnsq</a>.
+</p>
+
+<h2> Notes </h2>
+
+<p>
+ s6-dnsip4-filter does not perform DNS resolutions itself. Instead, if forks
+a <a href="skadns/skadnsd.html">skadnsd</a> child and sends it queries, getting
+the results asynchronously. The s6-dns filter programs have actually been
+written as example uses of the <a href="skadns/">skadns library</a>.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-dnsip4.html b/doc/s6-dnsip4.html
new file mode 100644
index 0000000..72f585b
--- /dev/null
+++ b/doc/s6-dnsip4.html
@@ -0,0 +1,63 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6-dnsip4 program</title>
+ <meta name="Description" content="s6-dns: the s6-dnsip4 program" />
+ <meta name="Keywords" content="s6-dns client s6-dnsip4 dnsip domain name ip address ipv4" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-dnsip4 program </h1>
+
+<p>
+ s6-dnsip4 finds the IPv4 addresses associated to a domain name.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+ s6-dnsip4 [ -q ] [ -r ] [ -t <em>timeout</em> ] <em>domain</em>
+</pre>
+
+<ul>
+ <li> s6-dnsip4 makes an A query for the name <em>domain</em>. It
+waits for the result and prints the obtained IPv4 addresses, one per line,
+then exits 0. </li>
+ <li> If the domain exists but no relevant field has been found, it exits 1. </li>
+ <li> If the DNS answered but no answer is available, it prints a relevant
+error message and exits 2. </li>
+ <li> By default, s6-dnsip4 looks for DNS cache addresses in the
+<tt>/etc/resolv.conf</tt> file. If the DNSCACHEIP environment variable is set
+and contains a list of IP (v4 or v6) addresses, separated by commas,
+semicolons, spaces, tabs, newlines or carriage returns, then this list
+is used instead. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-q</tt>&nbsp;: qualify. Qualifies <em>domain</em> before resolution,
+according to suffixes found in <tt>/etc/resolv.conf</tt>. If the DNSQUALIFY
+environment variable is set and contains a list of suffixes separated by spaces,
+tabs, newlines or carriage returns, then this list is used instead. By
+default, no qualification is used: if <em>domain</em> is not a FQDN, a dot
+is just appended to it. </li>
+ <li> <tt>-r</tt>&nbsp;: random. By default, the program does not sort the
+result, but prints them in the order received from the DNS. With this
+option, it performs a random permutation on the results before printing
+them. </li>
+ <li> <tt>-t</tt>&nbsp;<em>timeout</em>&nbsp;: if the resolution takes more
+than <em>timeout</em> milliseconds, then it exits 99 right away with an error
+message. By default, <em>timeout</em> is 0, which means no timeout. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-dnsip6-filter.html b/doc/s6-dnsip6-filter.html
new file mode 100644
index 0000000..7cbad6b
--- /dev/null
+++ b/doc/s6-dnsip6-filter.html
@@ -0,0 +1,99 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6-dnsip6-filter program</title>
+ <meta name="Description" content="s6-dns: the s6-dnsip6-filter program" />
+ <meta name="Keywords" content="s6-dns client s6-dnsip6-filter dnsip domain name ip address ipv6 filter" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-dnsip6-filter program </h1>
+
+<p>
+ s6-dnsip6-filter reads domain names on its standard input and
+prints the corresponding IPv6 addresses on its standard output.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+ s6-dnsip6-filter [ -l <em>maxlines</em> ] [ -c <em>maxconn</em> ] [ -t <em>timeout</em> ] [ -f <em>normalfmt</em> ] [ -e <em>errorfmt</em> ]
+</pre>
+
+<ul>
+ <li> s6-dnsip6-filter reads lines from its stdin, processes them, and prints
+the processed lines to its stdout, in the same order. It exits 0 when it
+reads EOF on stdin. </li>
+ <li> Every line starting with a domain name is processed. Lines not
+starting with a domain name are not, and the formatting rules treat them
+as a pure "remainder". </li>
+ <li> For every processed line, an AAAA query is made, and the result is
+printed according to the formatting rules. The domain is not qualified before
+being resolved. </li>
+ <li> By default, s6-dnsip6-filter looks for DNS cache addresses in the
+<tt>/etc/resolv.conf</tt> file. If the DNSCACHEIP environment variable is set
+and contains a list of IP (v4 or v6) addresses, separated by commas,
+semicolons, spaces, tabs, newlines or carriage returns, then this list
+is used instead. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-l</tt>&nbsp;<em>maxlines</em>&nbsp;: maximum lines. s6-dnsip6-filter
+will keep at most <em>maxlines</em> lines in memory at the same time. If line
+<em>n</em> is still waiting for resolution and cannot be printed, then
+s6-dnsip6-filter will stop reading from stdin before line <em>n+maxlines</em>,
+until the result arrives for line <em>n</em>. By default, <em>maxlines</em>
+is 256. </li>
+ <li> <tt>-c</tt>&nbsp;<em>maxconn</em>&nbsp;: maximum concurrent resolutions.
+s6-dnsip6-filter will perform at most <em>maxconn</em> resolutions in
+parallel at the same time. <em>maxlines</em> cannot be lesser than <em>maxconn</em>.
+By default, <em>maxconn</em> is 128. </li>
+ <li> <tt>-t</tt>&nbsp;<em>timeout</em>&nbsp;: if any resolution takes more
+than <em>timeout</em> milliseconds, then it is aborted and printed as a
+timeout error. By default, <em>timeout</em> is 0, which means no timeout. </li>
+ <li> <tt>-f</tt>&nbsp;<em>normalfmt</em>&nbsp;: print the positive results according to
+the <em>normalfmt</em> format string. By default, <em>normalfmt</em> is
+<tt>%s=%d%w%r</tt>, which means: print the original domain name, then an equal
+sign, then the corresponding IPv6 address, then the remainder of the line. </li>
+ <li> <tt>-e</tt>&nbsp;<em>errorfmt</em>&nbsp;: print the negative results according to
+the <em>errorfmt</em> format string. By default, <em>errorfmt</em> is
+<tt>%s=&lt;%e%&gt;%w%r</tt>, which means: print the original domain name, then an equal
+sign, then the error message between angle brackets, then the remainder of the line. </li>
+</ul>
+
+<h2> Formatting rules </h2>
+
+<p>
+ <em>normalfmt</em> and <em>errorfmt</em> are format strings, i.e. they tell the
+program how a line must be printed. The following sequences are recognized:
+</p>
+
+<ul>
+ <li> <tt>%%</tt> prints a single <tt>%</tt> character </li>
+ <li> <tt>%s</tt> prints the original domain name </li>
+ <li> <tt>%d</tt> prints the IP address, if any </li>
+ <li> <tt>%e</tt> prints the error message, if any </li>
+ <li> <tt>%w%r</tt> prints the remainder of the input line </li>
+</ul>
+
+<h2> Notes </h2>
+
+<p>
+ s6-dnsip6-filter does not perform DNS resolutions itself. Instead, if forks
+a <a href="skadns/skadnsd.html">skadnsd</a> child and sends it queries, getting
+the results asynchronously. The s6-dns filter programs have actually been
+written as example uses of the <a href="skadns/">skadns library</a>.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-dnsip6.html b/doc/s6-dnsip6.html
new file mode 100644
index 0000000..d417011
--- /dev/null
+++ b/doc/s6-dnsip6.html
@@ -0,0 +1,73 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6-dnsip6 program</title>
+ <meta name="Description" content="s6-dns: the s6-dnsip6 program" />
+ <meta name="Keywords" content="s6-dns client s6-dnsip6 dnsip domain name ip address ipv6" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-dnsip6 program </h1>
+
+<p>
+ s6-dnsip6 finds the IPv6 addresses associated to a domain name.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+ s6-dnsip6 [ -q ] [ -r ] [ -t <em>timeout</em> ] <em>domain</em>
+</pre>
+
+<ul>
+ <li> s6-dnsip6 makes an AAAA query for the name <em>domain</em>. It
+waits for the result and prints the obtained IPv6 addresses, one per line,
+then exits 0. </li>
+ <li> If the domain exists but no relevant field has been found, it exits 1. </li>
+ <li> If the DNS answered but no answer is available, it prints a relevant
+error message and exits 2. </li>
+ <li> By default, s6-dnsip6 looks for DNS cache addresses in the
+<tt>/etc/resolv.conf</tt> file. If the DNSCACHEIP environment variable is set
+and contains a list of IP (v4 or v6) addresses, separated by commas,
+semicolons, spaces, tabs, newlines or carriage returns, then this list
+is used instead. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-q</tt>&nbsp;: qualify. Qualifies <em>domain</em> before resolution,
+according to suffixes found in <tt>/etc/resolv.conf</tt>. If the DNSQUALIFY
+environment variable is set and contains a list of suffixes separated by spaces,
+tabs, newlines or carriage returns, then this list is used instead. By
+default, no qualification is used: if <em>domain</em> is not a FQDN, a dot
+is just appended to it. </li>
+ <li> <tt>-r</tt>&nbsp;: random. By default, the program does not sort the
+result, but prints them in the order received from the DNS. With this
+option, it performs a random permutation on the results before printing
+them. </li>
+ <li> <tt>-t</tt>&nbsp;<em>timeout</em>&nbsp;: if the resolution takes more
+than <em>timeout</em> milliseconds, then it exits 99 right away with an error
+message. By default, <em>timeout</em> is 0, which means no timeout. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<p>
+ Bear in mind that <em>making AAAA queries</em> is very different, and
+totally independent, from <em>using IPv6 transport for the DNS queries</em>.
+Even if the underlying skalibs has been compiled without IPv6 support,
+or IPv6 DNS transport is unavailable for any reason, you can still perform
+AAAA queries and s6-dnsip6 will answer correctly.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-dnsmx.html b/doc/s6-dnsmx.html
new file mode 100644
index 0000000..1396aed
--- /dev/null
+++ b/doc/s6-dnsmx.html
@@ -0,0 +1,64 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6-dnsmx program</title>
+ <meta name="Description" content="s6-dns: the s6-dnsmx program" />
+ <meta name="Keywords" content="s6-dns client s6-dnsmx dnsmx domain name mx mail exchanger" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-dnsmx program </h1>
+
+<p>
+ s6-dnsmx finds the MX information associated to a domain name.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+ s6-dnsmx [ -q ] [ -r ] [ -t <em>timeout</em> ] <em>domain</em>
+</pre>
+
+<ul>
+ <li> s6-dnsmx makes an MX query for the name <em>domain</em>. It
+waits for the result and prints the obtained information, one exchanger
+per line, then exits 0. It prints the preference, then a space, then
+the exchanger name. </li>
+ <li> If the domain exists but no relevant field has been found, it exits 1. </li>
+ <li> If the DNS answered but no answer is available, it prints a relevant
+error message and exits 2. </li>
+ <li> By default, s6-dnsmx looks for DNS cache addresses in the
+<tt>/etc/resolv.conf</tt> file. If the DNSCACHEIP environment variable is set
+and contains a list of IP (v4 or v6) addresses, separated by commas,
+semicolons, spaces, tabs, newlines or carriage returns, then this list
+is used instead. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-q</tt>&nbsp;: qualify. Qualifies <em>domain</em> before resolution,
+according to suffixes found in <tt>/etc/resolv.conf</tt>. If the DNSQUALIFY
+environment variable is set and contains a list of suffixes separated by spaces,
+tabs, newlines or carriage returns, then this list is used instead. By
+default, no qualification is used: if <em>domain</em> is not a FQDN, a dot
+is just appended to it. </li>
+ <li> <tt>-r</tt>&nbsp;: random. By default, the program does not sort the
+result, but prints them in the order received from the DNS. With this
+option, it performs a random permutation on the results before printing
+them. </li>
+ <li> <tt>-t</tt>&nbsp;<em>timeout</em>&nbsp;: if the resolution takes more
+than <em>timeout</em> milliseconds, then it exits 99 right away with an error
+message. By default, <em>timeout</em> is 0, which means no timeout. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-dnsname-filter.html b/doc/s6-dnsname-filter.html
new file mode 100644
index 0000000..1554270
--- /dev/null
+++ b/doc/s6-dnsname-filter.html
@@ -0,0 +1,103 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6-dnsname-filter program</title>
+ <meta name="Description" content="s6-dns: the s6-dnsname-filter program" />
+ <meta name="Keywords" content="s6-dns client s6-dnsname-filter dnsname domain name ptr ip address filter" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-dnsname-filter program </h1>
+
+<p>
+ s6-dnsname-filter reads IP addresses on its standard input and
+prints the corresponding domain names on its standard output.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+ s6-dnsname-filter [ -4 ] [ -6 ] [ -l <em>maxlines</em> ] [ -c <em>maxconn</em> ] [ -t <em>timeout</em> ] [ -f <em>normalfmt</em> ] [ -e <em>errorfmt</em> ]
+</pre>
+
+<ul>
+ <li> s6-dnsname-filter reads lines from its stdin, processes them, and prints
+the processed lines to its stdout, in the same order. It exits 0 when it
+reads EOF on stdin. </li>
+ <li> Every line starting with an IP address is processed. Lines not
+starting with an IP address are not, and the formatting rules treat them
+as a pure "remainder". </li>
+ <li> For every processed line, the read IP address is converted to an
+<tt>in-addr.arpa.</tt> or <tt>ip6.arpa.</tt> domain on which a PTR query is made,
+and the result is printed according to the formatting rules. </li>
+ <li> By default, s6-dnsname-filter looks for DNS cache addresses in the
+<tt>/etc/resolv.conf</tt> file. If the DNSCACHEIP environment variable is set
+and contains a list of IP (v4 or v6) addresses, separated by commas,
+semicolons, spaces, tabs, newlines or carriage returns, then this list
+is used instead. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-4</tt>&nbsp;: scan for IPv4 addresses. </li>
+ <li> <tt>-6</tt>&nbsp;: scan for IPv6 addresses. Both the <tt>-4</tt> and
+<tt>-6</tt> options may be given. If none is given, by default
+s6-dnsname-filter will only scan for IPv4 addresses. </li>
+ <li> <tt>-l</tt>&nbsp;<em>maxlines</em>&nbsp;: maximum lines. s6-dnsname-filter
+will keep at most <em>maxlines</em> lines in memory at the same time. If line
+<em>n</em> is still waiting for resolution and cannot be printed, then
+s6-dnsname-filter will stop reading from stdin before line <em>n+maxlines</em>,
+until the result arrives for line <em>n</em>. By default, <em>maxlines</em>
+is 256. </li>
+ <li> <tt>-c</tt>&nbsp;<em>maxconn</em>&nbsp;: maximum concurrent resolutions.
+s6-dnsname-filter will perform at most <em>maxconn</em> resolutions in
+parallel at the same time. <em>maxlines</em> cannot be lesser than <em>maxconn</em>.
+By default, <em>maxconn</em> is 128. </li>
+ <li> <tt>-t</tt>&nbsp;<em>timeout</em>&nbsp;: if any resolution takes more
+than <em>timeout</em> milliseconds, then it is aborted and printed as a
+timeout error. By default, <em>timeout</em> is 0, which means no timeout. </li>
+ <li> <tt>-f</tt>&nbsp;<em>normalfmt</em>&nbsp;: print the positive results according to
+the <em>normalfmt</em> format string. By default, <em>normalfmt</em> is
+<tt>%s=%d%w%r</tt>, which means: print the original IP address, then an equal
+sign, then the corresponding domain name, then the remainder of the line. </li>
+ <li> <tt>-e</tt>&nbsp;<em>errorfmt</em>&nbsp;: print the negative results according to
+the <em>errorfmt</em> format string. By default, <em>errorfmt</em> is
+<tt>%s=&lt;%e%&gt;%w%r</tt>, which means: print the original IP address, then an equal
+sign, then the error message between angle brackets, then the remainder of the line. </li>
+</ul>
+
+<h2> Formatting rules </h2>
+
+<p>
+ <em>normalfmt</em> and <em>errorfmt</em> are format strings, i.e. they tell the
+program how a line must be printed. The following sequences are recognized:
+</p>
+
+<ul>
+ <li> <tt>%%</tt> prints a single <tt>%</tt> character </li>
+ <li> <tt>%s</tt> prints the original IP address </li>
+ <li> <tt>%d</tt> prints the domain name, if any </li>
+ <li> <tt>%e</tt> prints the error message, if any </li>
+ <li> <tt>%w%r</tt> prints the remainder of the input line </li>
+</ul>
+
+<h2> Notes </h2>
+
+<p>
+ s6-dnsname-filter does not perform DNS resolutions itself. Instead, if forks
+a <a href="skadns/skadnsd.html">skadnsd</a> child and sends it queries, getting
+the results asynchronously. The s6-dns filter programs have actually been
+written as example uses of the <a href="skadns/">skadns library</a>.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-dnsname.html b/doc/s6-dnsname.html
new file mode 100644
index 0000000..0c0c5b0
--- /dev/null
+++ b/doc/s6-dnsname.html
@@ -0,0 +1,72 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6-dnsname program</title>
+ <meta name="Description" content="s6-dns: the s6-dnsname program" />
+ <meta name="Keywords" content="s6-dns client s6-dnsname dnsname domain name ip address ipv4 ipv6" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-dnsname program </h1>
+
+<p>
+ s6-dnsname finds the name associated to an IPv4 or IPv6 address.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+ s6-dnsname [ -4 | -6 ] [ -r ] [ -t <em>timeout</em> ] <em>ip</em>
+</pre>
+
+<ul>
+ <li> s6-dnsname converts the IP address <em>ip</em> to a name
+ending in <tt>in-addr.arpa.</tt> or <tt>ip6.arpa.</tt> then makes a
+PTR query for this name.
+It waits for the result and prints the obtained names, one per line,
+then exits 0. </li>
+ <li> If the domain exists but no relevant field has been found, it exits 1. </li>
+ <li> If the DNS answered but no answer is available, it prints a relevant
+error message and exits 2. </li>
+ <li> By default, s6-dnsname looks for DNS cache addresses in the
+<tt>/etc/resolv.conf</tt> file. If the DNSCACHEIP environment variable is set
+and contains a list of IP (v4 or v6) addresses, separated by commas,
+semicolons, spaces, tabs, newlines or carriage returns, then this list
+is used instead. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-4</tt>&nbsp;: interpret <em>ip</em> as an IPv4 address. </li>
+ <li> <tt>-6</tt>&nbsp;: interpret <em>ip</em> as an IPv6 address.
+If neither of the <tt>-4</tt> and <tt>-6</tt> is given, or if both are
+given, then <em>ip</em> will be interpreted as v4 or v6 depending on
+its syntax. </li>
+ <li> <tt>-r</tt>&nbsp;: random. By default, the program does not sort the
+result, but prints them in the order received from the DNS. With this
+option, it performs a random permutation on the results before printing
+them. </li>
+ <li> <tt>-t</tt>&nbsp;<em>timeout</em>&nbsp;: if the resolution takes more
+than <em>timeout</em> milliseconds, then it exits 99 right away with an error
+message. By default, <em>timeout</em> is 0, which means no timeout. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> If the underlying skalibs has been compiled with IPv6 support disabled,
+s6-dnsname will not be able to use IPv6 transport for its resolution, but it
+will still accept and resolve IPv6 addresses. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-dnsns.html b/doc/s6-dnsns.html
new file mode 100644
index 0000000..c15a7df
--- /dev/null
+++ b/doc/s6-dnsns.html
@@ -0,0 +1,63 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6-dnsns program</title>
+ <meta name="Description" content="s6-dns: the s6-dnsns program" />
+ <meta name="Keywords" content="s6-dns client s6-dnsns domain name nameserver ns rr field" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-dnsns program </h1>
+
+<p>
+ s6-dnsns finds the relevant nameservers providing data for a domain.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+ s6-dnsns [ -q ] [ -r ] [ -t <em>timeout</em> ] <em>domain</em>
+</pre>
+
+<ul>
+ <li> s6-dnsns makes an NS query for the name <em>domain</em>. It
+waits for the result and prints the obtained information, one name
+per line, then exits 0. </li>
+ <li> If the domain exists but no relevant field has been found, it exits 1. </li>
+ <li> If the DNS answered but no answer is available, it prints a relevant
+error message and exits 2. </li>
+ <li> By default, s6-dnsns looks for DNS cache addresses in the
+<tt>/etc/resolv.conf</tt> file. If the DNSCACHEIP environment variable is set
+and contains a list of IP (v4 or v6) addresses, separated by commas,
+semicolons, spaces, tabs, newlines or carriage returns, then this list
+is used instead. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-q</tt>&nbsp;: qualify. Qualifies <em>domain</em> before resolution,
+according to suffixes found in <tt>/etc/resolv.conf</tt>. If the DNSQUALIFY
+environment variable is set and contains a list of suffixes separated by spaces,
+tabs, newlines or carriage returns, then this list is used instead. By
+default, no qualification is used: if <em>domain</em> is not a FQDN, a dot
+is just appended to it. </li>
+ <li> <tt>-r</tt>&nbsp;: random. By default, the program does not sort the
+result, but prints them in the order received from the DNS. With this
+option, it performs a random permutation on the results before printing
+them. </li>
+ <li> <tt>-t</tt>&nbsp;<em>timeout</em>&nbsp;: if the resolution takes more
+than <em>timeout</em> milliseconds, then it exits 99 right away with an error
+message. By default, <em>timeout</em> is 0, which means no timeout. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-dnsq.html b/doc/s6-dnsq.html
new file mode 100644
index 0000000..9a22602
--- /dev/null
+++ b/doc/s6-dnsq.html
@@ -0,0 +1,82 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6-dnsq program</title>
+ <meta name="Description" content="s6-dns: the s6-dnsq program" />
+ <meta name="Keywords" content="s6-dns client s6-dnsq dnsq analysis debug" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-dnsq program </h1>
+
+<p>
+ s6-dnsq is an analysis and debug tool. It performs a non-recursive DNS query
+to a given list of servers,
+then prints the contents of the answer packet, and optionally debug
+information during the resolution.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+ s6-dnsq [ -1 | -2 ] [ -t <em>timeout</em> ] [ -D <em>level</em> ] <em>qtype</em> <em>domain</em> <em>serverlist</em>
+</pre>
+
+<ul>
+ <li> s6-dnsq makes an iterative DNS query of type <em>qtype</em> for the name <em>domain</em>
+to <em>serverlist</em>.
+It prints the answer packet, in human-readable form, to its standard output,
+then exits 0. </li>
+ <li> It does not qualify <em>domain</em>. </li>
+ <li> If the resolution fails for some reason, s6-dnsq exits 2. </li>
+ <li> <em>serverlist</em> is a list of IP addresses (v4 or v6), one or more addresses
+per argument on the command line (so you can use, for instance,
+<tt>`s6-dnsip4 ns.example.com`</tt> as argument, and get all the addresses for
+<tt>ns.example.com</tt> in your server list).
+Servers are tried in the order given, until a definitive
+answer is obtained or s6-dnsq runs out of server addresses. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-1</tt>&nbsp;: send debug information to stdout. </li>
+ <li> <tt>-2</tt>&nbsp;: send debug information to stderr. This is the default. Note that
+those options only apply to debug output, not to the regular packet dump, which
+is always printed to stdout. </li>
+ <li> <tt>-t</tt>&nbsp;<em>timeout</em>&nbsp;: if the resolution takes more
+than <em>timeout</em> milliseconds, then it exits 99 right away with an error
+message. By default, <em>timeout</em> is 0, which means no timeout. </li>
+ <li> <tt>-D</tt>&nbsp;<em>level</em>&nbsp;: produce debug output during
+resolution. If <em>level</em> is:
+ <ul>
+ <li> 0: no debug output is produced </li>
+ <li> 1: information is printed when s6-dnsq receives a DNS packet from a server </li>
+ <li> 2: information is printed before and after s6-dnsq sends a DNS packet to a server </li>
+ <li> 3: both 1. and 2. apply </li>
+ </ul>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> TXT records are printed in a quoted form similar to
+<a href="http://skarnet.org/software/s6-portable-utils/s6-quote.html">s6-quote</a>'s
+output. </li>
+ <li> If s6-dnsq finds a record it cannot print, such as an unknown RR type,
+it dumps its contents in the same quoted form. </li>
+ <li> The normal output format should be stable, so you can write programs that
+automatically parse it. However, the debug output format is undocumented and subject
+to change. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-dnsqr.html b/doc/s6-dnsqr.html
new file mode 100644
index 0000000..ce308bd
--- /dev/null
+++ b/doc/s6-dnsqr.html
@@ -0,0 +1,80 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6-dnsqr program</title>
+ <meta name="Description" content="s6-dns: the s6-dnsqr program" />
+ <meta name="Keywords" content="s6-dns client s6-dnsqr dnsqr analysis debug" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-dnsqr program </h1>
+
+<p>
+ s6-dnsqr is an analysis and debug tool. It performs a DNS resolution,
+then prints the contents of the answer packet, and optionally debug
+information during the resolution.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+ s6-dnsqr [ -1 | -2 ] [ -t <em>timeout</em> ] [ -D <em>level</em> ] <em>qtype</em> <em>domain</em>
+</pre>
+
+<ul>
+ <li> s6-dnsqr makes a recursive DNS query of type <em>qtype</em> for the name <em>domain</em>.
+It prints the answer packet, in human-readable form, to its standard output,
+then exits 0. </li>
+ <li> It does not qualify <em>domain</em>. </li>
+ <li> If the resolution fails for some reason, s6-dnsqr exits 2. </li>
+ <li> By default, s6-dnsqr looks for DNS cache addresses in the
+<tt>/etc/resolv.conf</tt> file. If the DNSCACHEIP environment variable is set
+and contains a list of IP (v4 or v6) addresses, separated by commas,
+semicolons, spaces, tabs, newlines or carriage returns, then this list
+is used instead. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-1</tt>&nbsp;: send debug information to stdout. </li>
+ <li> <tt>-2</tt>&nbsp;: send debug information to stderr. This is the default. Not that
+those options only apply to debug output, not to the regular packet dump, which
+is always printed to stdout. </li>
+ <li> <tt>-t</tt>&nbsp;<em>timeout</em>&nbsp;: if the resolution takes more
+than <em>timeout</em> milliseconds, then it exits 99 right away with an error
+message. By default, <em>timeout</em> is 0, which means no timeout. </li>
+ <li> <tt>-D</tt>&nbsp;<em>level</em>&nbsp;: produce debug output during
+resolution. If <em>level</em> is:
+ <ul>
+ <li> 0: no debug output is produced </li>
+ <li> 1: information is printed when s6-dnsqr receives a DNS packet from a cache </li>
+ <li> 2: information is printed before and after s6-dnsqr sends a DNS packet to a cache </li>
+ <li> 3: both 1. and 2. apply </li>
+ </ul>
+
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> TXT records are printed in a quoted form similar to
+<a href="http://skarnet.org/software/s6-portable-utils/s6-quote.html">s6-quote</a>'s
+output. </li>
+ <li> If s6-dnsqr finds a record it cannot print, such as an unknown RR type,
+it dumps its content in the same quoted form. </li>
+ <li> The normal output format should be stable, so you can write programs that
+automatically parse it. However, the debug output format is undocumented and subject
+to change. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-dnsqualify.html b/doc/s6-dnsqualify.html
new file mode 100644
index 0000000..f515be7
--- /dev/null
+++ b/doc/s6-dnsqualify.html
@@ -0,0 +1,41 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6-dnsqualify program</title>
+ <meta name="Description" content="s6-dns: the s6-dnsqualify program" />
+ <meta name="Keywords" content="s6-dns s6-dnsqualify domain name qualification fqdn resolv.conf" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-dnsqualify program </h1>
+
+<p>
+ s6-dnsqualify qualifies a domain name.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+ s6-dnsqualify <em>domain</em>
+</pre>
+
+<ul>
+ <li> s6-dnsqualify qualifies <em>domain</em> according to the "domain" and
+"search" rules in <tt>/etc/resolv.conf</tt>. It prints the result on
+stdout, one FQDN per line. </li>
+ <li> If the DNSQUALIFY
+environment variable is set and contains a list of suffixes separated by spaces,
+tabs, newlines or carriage returns, then this list is used instead of the
+<tt>/etc/resolv.conf</tt> rules. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-dnssoa.html b/doc/s6-dnssoa.html
new file mode 100644
index 0000000..6f4a8f2
--- /dev/null
+++ b/doc/s6-dnssoa.html
@@ -0,0 +1,65 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6-dnssoa program</title>
+ <meta name="Description" content="s6-dns: the s6-dnssoa program" />
+ <meta name="Keywords" content="s6-dns client s6-dnssoa domain soa start of authority rr field" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-dnssoa program </h1>
+
+<p>
+ s6-dnssoa finds the SOA information associated to a domain.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+ s6-dnssoa [ -q ] [ -r ] [ -t <em>timeout</em> ] <em>domain</em>
+</pre>
+
+<ul>
+ <li> s6-dnsmx makes an SOA query for the name <em>domain</em>. It
+waits for the result and prints the obtained information line by line,
+then exits 0. It prints the mname, the rname, the serial number, and
+the refresh, retry, expiration and minimum times, in that order,
+separated by spaces. </li>
+ <li> If the domain exists but no relevant field has been found, it exits 1. </li>
+ <li> If the DNS answered but no answer is available, it prints a relevant
+error message and exits 2. </li>
+ <li> By default, s6-dnssoa looks for DNS cache addresses in the
+<tt>/etc/resolv.conf</tt> file. If the DNSCACHEIP environment variable is set
+and contains a list of IP (v4 or v6) addresses, separated by commas,
+semicolons, spaces, tabs, newlines or carriage returns, then this list
+is used instead. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-q</tt>&nbsp;: qualify. Qualifies <em>domain</em> before resolution,
+according to suffixes found in <tt>/etc/resolv.conf</tt>. If the DNSQUALIFY
+environment variable is set and contains a list of suffixes separated by spaces,
+tabs, newlines or carriage returns, then this list is used instead. By
+default, no qualification is used: if <em>domain</em> is not a FQDN, a dot
+is just appended to it. </li>
+ <li> <tt>-r</tt>&nbsp;: random. By default, the program does not sort the
+result, but prints them in the order received from the DNS. With this
+option, it performs a random permutation on the results before printing
+them. </li>
+ <li> <tt>-t</tt>&nbsp;<em>timeout</em>&nbsp;: if the resolution takes more
+than <em>timeout</em> milliseconds, then it exits 99 right away with an error
+message. By default, <em>timeout</em> is 0, which means no timeout. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-dnssrv.html b/doc/s6-dnssrv.html
new file mode 100644
index 0000000..2a88408
--- /dev/null
+++ b/doc/s6-dnssrv.html
@@ -0,0 +1,65 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6-dnssrv program</title>
+ <meta name="Description" content="s6-dns: the s6-dnssrv program" />
+ <meta name="Keywords" content="s6-dns client s6-dnssrv domain name srv rr field service" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-dnssrv program </h1>
+
+<p>
+ s6-dnssrv finds the SRV information associated to a
+service name, protocol name and domain name.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+ s6-dnssrv [ -q ] [ -r ] [ -t <em>timeout</em> ] <em>service</em> <em>proto</em> <em>domain</em>
+</pre>
+
+<ul>
+ <li> s6-dnssrv makes an SRV query for _<em>service</em>._<em>proto</em>.<em>domain</em>.
+It waits for the result and prints the obtained information line by line,
+then exits 0. It prints the priority, the weight, the port number and the
+target, in that order, separated by spaces. </li>
+ <li> If the domain exists but no relevant field has been found, it exits 1. </li>
+ <li> If the DNS answered but no answer is available, it prints a relevant
+error message and exits 2. </li>
+ <li> By default, s6-dnssrv looks for DNS cache addresses in the
+<tt>/etc/resolv.conf</tt> file. If the DNSCACHEIP environment variable is set
+and contains a list of IP (v4 or v6) addresses, separated by commas,
+semicolons, spaces, tabs, newlines or carriage returns, then this list
+is used instead. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-q</tt>&nbsp;: qualify. Qualifies <em>domain</em> before resolution,
+according to suffixes found in <tt>/etc/resolv.conf</tt>. If the DNSQUALIFY
+environment variable is set and contains a list of suffixes separated by spaces,
+tabs, newlines or carriage returns, then this list is used instead. By
+default, no qualification is used: if <em>domain</em> is not a FQDN, a dot
+is just appended to it. </li>
+ <li> <tt>-r</tt>&nbsp;: random. By default, the program does not sort the
+result, but prints them in the order received from the DNS. With this
+option, it performs a random permutation on the results before printing
+them. </li>
+ <li> <tt>-t</tt>&nbsp;<em>timeout</em>&nbsp;: if the resolution takes more
+than <em>timeout</em> milliseconds, then it exits 99 right away with an error
+message. By default, <em>timeout</em> is 0, which means no timeout. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-dnstxt.html b/doc/s6-dnstxt.html
new file mode 100644
index 0000000..fa84174
--- /dev/null
+++ b/doc/s6-dnstxt.html
@@ -0,0 +1,77 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6-dnstxt program</title>
+ <meta name="Description" content="s6-dns: the s6-dnstxt program" />
+ <meta name="Keywords" content="s6-dns client s6-dnstxt dnstxt domain name txt text rr field" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-dnstxt program </h1>
+
+<p>
+ s6-dnstxt finds the TXT information associated to a domain name.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+ s6-dnstxt [ -q ] [ -r ] [ -t <em>timeout</em> ] <em>domain</em>
+</pre>
+
+<ul>
+ <li> s6-dnstxt makes a TXT query for the name <em>domain</em>. It
+waits for the result and prints the obtained strings, one by line,
+in a quoted form similar to
+<a href="http://skarnet.org/software/s6-portable-utils/s6-quote.html">s6-quote</a>'s
+output. You can pipe s6-dnstxt's output through
+<a href="http://skarnet.org/software/s6-portable-utils/s6-unquote-filter.html">s6-unquote-filter</a>
+to get unquoted TXT fields. </li>
+ <li> If the domain exists but no relevant field has been found, it exits 1. </li>
+ <li> If the DNS answered but no answer is available, it prints a relevant
+error message and exits 2. </li>
+ <li> By default, s6-dnstxt looks for DNS cache addresses in the
+<tt>/etc/resolv.conf</tt> file. If the DNSCACHEIP environment variable is set
+and contains a list of IP (v4 or v6) addresses, separated by commas,
+semicolons, spaces, tabs, newlines or carriage returns, then this list
+is used instead. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-q</tt>&nbsp;: qualify. Qualifies <em>domain</em> before resolution,
+according to suffixes found in <tt>/etc/resolv.conf</tt>. If the DNSQUALIFY
+environment variable is set and contains a list of suffixes separated by spaces,
+tabs, newlines or carriage returns, then this list is used instead. By
+default, no qualification is used: if <em>domain</em> is not a FQDN, a dot
+is just appended to it. </li>
+ <li> <tt>-r</tt>&nbsp;: random. By default, the program does not sort the
+result, but prints them in the order received from the DNS. With this
+option, it performs a random permutation on the results before printing
+them. </li>
+ <li> <tt>-t</tt>&nbsp;<em>timeout</em>&nbsp;: if the resolution takes more
+than <em>timeout</em> milliseconds, then it exits 99 right away with an error
+message. By default, <em>timeout</em> is 0, which means no timeout. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<p>
+ There can be more than one character-string in a TXT RR, and there can be
+more than one TXT RR per domain. DNS clients usually concatenate all this
+and only give one string. s6-dnstxt concatenates all the character-strings
+in one TXT record, but separates different TXT records, printing each one
+on a separate line.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-randomip.html b/doc/s6-randomip.html
new file mode 100644
index 0000000..0ad3b46
--- /dev/null
+++ b/doc/s6-randomip.html
@@ -0,0 +1,57 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the s6-randomip program</title>
+ <meta name="Description" content="s6-dns: the s6-randomip program" />
+ <meta name="Keywords" content="s6-dns s6-randomip randomip random ip address filter" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-randomip program </h1>
+
+<p>
+ s6-randomip generates random IP addresses and prints them to
+its standard output, one per line.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+ s6-randomip [ -4 ] [ -6 ] [ -n <em>max</em> ]
+</pre>
+
+<ul>
+ <li> s6-randomip makes up IP addresses and prints
+them to its stdout. It keeps running until it receives a signal. </li>
+ <li> IP addresses are not guaranteed to represent anything. They are totally random. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-4</tt>&nbsp;: generate IPv4 addresses. </li>
+ <li> <tt>-6</tt>&nbsp;: generate IPv6 addresses. Both the <tt>-4</tt> and
+<tt>-6</tt> options may be given. If none is given, by default
+s6-randomip will only generate IPv4 addresses. </li>
+ <li> <tt>-n</tt>&nbsp;<em>max</em>&nbsp;: s6-randomip will only generate
+<em>max</em> addresses, then exit 0, instead of running forever. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<p>
+ s6-randomip can be used in conjunction with
+<a href="s6-dnsname-filter">s6-dnsname-filter</a> to stress-test your
+DNS architecture. Please use this responsibly and ethically.
+</p>
+
+</body>
+</html>
diff --git a/doc/skadns/index.html b/doc/skadns/index.html
new file mode 100644
index 0000000..f02f2dd
--- /dev/null
+++ b/doc/skadns/index.html
@@ -0,0 +1,262 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the skadns library interface</title>
+ <meta name="Description" content="s6-dns: the skadns library interface" />
+ <meta name="Keywords" content="s6-dns skadns library asynchronous resolution resolver client C interface" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="../index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>skadns</tt> library interface </h1>
+
+<p>
+ The <tt>skadns</tt> library provides an API for asynchronous DNS
+resolution.
+</p>
+
+<h2> Compiling </h2>
+
+<ul>
+<ul>
+ <li> Make sure the s6-dns headers, as well as the skalibs headers,
+are visible in your header search path. </li>
+ <li> Use <tt>#include &lt;s6-dns/skadns.h&gt;</tt> </li>
+ <li> You might also want to include the <tt>s6-dns/s6dns.h</tt> header for
+the definition of the <tt>s6dns_domain_t</tt> type and the various
+qtype constants </li>
+</ul>
+
+<h2> Linking </h2>
+
+<ul>
+ <li> Make sure the s6 libraries, as well as the skalibs libraries,
+are visible in your library search path. </li>
+ <li> Link against <tt>-lskadns</tt> and <tt>-lskarnet</tt>. </li>
+ <li> 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. </li>
+</ul>
+
+
+<h2> Programming </h2>
+
+<p>
+ Check the <tt>s6-dns/skadns.h</tt> header for the
+exact function prototypes.
+</p>
+
+<p>
+ 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 <tt>waitpid()</tt>s. The best
+practice is to use a
+<a href="http://skarnet.org/software/skalibs/libstddjb/selfpipe.html">self-pipe</a>
+to handle SIGCHLD (as well as other signals the application needs to trap),
+and to <em>always</em> use <tt>wait_nohang()</tt> to reap children,
+simply ignoring pids you don't know.
+</p>
+
+<p>
+ If your (badly programmed) application has trouble handling unknown
+children, consider using a skadnsd service.
+</p>
+
+<h3> A programming example </h3>
+
+<p>
+ The <tt>src/clients/s6dns_generic_filter_main.c</tt> file in the s6-dns
+package, used in the <tt>s6-dns*-filter</tt> programs, illustrates how to
+use the skadns library.
+</p>
+
+<h3> Starting and ending a session </h3>
+
+<pre>
+skadns_t a = SKADNS_ZERO ;
+tain_t deadline, stamp ;
+
+tain_now(&amp;stamp) ;
+tain_addsec(&amp;deadline, &amp;stamp, 2)
+
+// char const *path = SKADNS_IPCPATH ;
+// skadns_start(&amp;a, path, &amp;deadline, &amp;stamp) ;
+skadns_startf(&amp;a, &amp;deadline, &amp;stamp) ;
+</pre>
+
+<p>
+<tt>skadns_start</tt> starts a session with a skadnsd service, listening
+on <em>path</em>. <br />
+<tt>skadns_startf</tt> starts a session with a skadnsd process as a child
+(which is the simplest usage). <br />
+<tt>a</tt> is a skadns_t structure that must be declared and
+initialized to SKADNS_ZERO.
+<tt>stamp</tt> must be an accurate enough timestamp. <br />
+If the session initialization fails, the function returns 0 and errno is set;
+else the function returns 1.
+</p>
+<p>
+If the absolute time <tt>deadline</tt> 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 <tt>stamp</tt> and <tt>deadline</tt> should be
+enough: if the function takes more than that to return, then there is a
+problem with the underlying processes.
+</p>
+
+<p>
+ You can have more than one session open in parallel, by declaring
+several distinct <tt>skadns_t</tt> structures and calling
+<tt>skadns_startf</tt> (or <tt>skadns_start</tt>) 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.
+</p>
+
+<pre>
+skadns_end(&amp;a) ;
+</pre>
+
+<p>
+<tt>skadns_end</tt> frees all the resources used by the session. The
+<tt>a</tt> structure is then reusable for another session.
+</p>
+
+<h3> Sending a DNS query </h3>
+
+<pre>
+s6dns_domain_t d ;
+uint16 qtype ;
+uint16 id ;
+tain_t limit, deadline, stamp ;
+
+skadns_send(&amp;a, &amp;id, &amp;d, qtype, &amp;limit, &amp;deadline, &amp;stamp) ;
+</pre>
+
+<p>
+<tt>skadns_send</tt> starts the asynchronous resolution of domain <em>d</em>
+with query type <em>qtype</em>. <em>d</em> must be encoded in packet form.
+<em>stamp</em> must be an accurate enough timetamp.
+If the resolution hasn't completed by deadline <em>limit</em>, it will
+automatically fail with a status set to ETIMEDOUT.
+</p>
+
+<p>
+ Like <tt>skadns_startf()</tt>, the <tt>skadns_send()</tt> call
+is synchronous but should not be blocking; however, if it hasn't returned by
+deadline <em>deadline</em>, it then returns 0 with errno set to ETIMEDOUT.
+On failure, the call returns 0 and sets errno. On success, it returns 1 and
+<em>id</em> is set to a 16-bit number identifying the query.
+</p>
+
+<h3> Cancelling a query </h3>
+
+<pre>
+skadns_cancel(&amp;a, id, &amp;deadline, &amp;stamp) ;
+</pre>
+
+<p>
+ <tt>skadns_cancel</tt> cancels the resolution identified by <em>id</em>.
+<em>stamp</em> 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 <em>deadline</em>, it then returns 0 ETIMEDOUT.
+</p>
+
+<p>
+ After a query has been successfully canceled, its id can be discarded.
+</p>
+
+<h3> Asynchronously waiting for answers </h3>
+
+<p>
+<em> (from now on, the functions are listed with their prototypes instead
+of usage examples.) </em>
+</p>
+
+<pre>
+int skadns_fd (skadns_t const *a)
+</pre>
+
+<p>
+ Returns a file descriptor to select on for reading. Do not
+<tt>read()</tt> it though.
+</p>
+
+<pre>
+int skadns_update (skadns_t *a)
+</pre>
+
+<p>
+ Call this function whenever the fd checks readability: it will
+update <em>a</em>'s internal structures with information from the
+<a href="skadnsd.html">skadnsd</a> daemon. It returns -1 if an error
+occurs; in case of success, it returns the number of identifiers for
+which something happened.
+</p>
+
+<p>
+ When <tt>skadns_update</tt> returns,
+<tt>genalloc_s(uint16, &amp;a-&gt;list)</tt> points to an array of
+<tt>genalloc_len(uint16, &amp;a-&gt;list)</tt> 16-bit unsigned
+integers. Those integers are valid ids that can be used with the
+following functions.
+</p>
+
+<pre>
+char const *skadns_packet (skadns_t *a, uint16 id)
+int skadns_packetlen (skadns_t *a, uint16 id)
+</pre>
+
+<p>
+ <tt>skadns_packet()</tt> returns a pointer to the DNS packet
+containing the answer to the query identified by <em>id</em>,
+and <tt>skadns_packetlen()</tt> returns its length. If an
+error has occurred, <tt>skadns_packet()</tt> returns NULL and
+<tt>skadns_packetlen()</tt> returns -1; either call sets errno
+to a value identifying the error. Some errno values have a
+special meaning:
+</p>
+
+<ul>
+ <li> EAGAIN: the result has not arrived yet. </li>
+ <li> ECANCELED: the query has been canceled. </li>
+ <li> EINVAL: <em>id</em> does not identify a valid query. Note
+that <strong>if</strong> you get EINVAL, <strong>then</strong>
+<em>id</em> is invalid, but the reverse is not true:
+using invalid ids is a programming error and may result in a crash. </li>
+</ul>
+
+<pre>
+int skadns_release (skadns_t *a, uint16 id)
+</pre>
+
+<p>
+ <tt>skadns_release()</tt> frees the cell holding the result of
+query <em>id</em>. It returns 1 on success, then <em>id</em> can
+be discarded - further calls to <tt>skadns_send()</tt> may reuse
+the same number.
+</p>
+
+<p>
+ <tt>skadns_release()</tt> 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:
+</p>
+
+<ul>
+ <li> EBUSY: the query is still pending, cancelled or not. </li>
+ <li> EINVAL: <em>id</em> is invalid.
+</ul>
+
+</body>
+</html>
diff --git a/doc/skadns/skadnsd.html b/doc/skadns/skadnsd.html
new file mode 100644
index 0000000..e8316b8
--- /dev/null
+++ b/doc/skadns/skadnsd.html
@@ -0,0 +1,120 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: the skadnsd internal program</title>
+ <meta name="Description" content="s6-dns: the skadnsd internal program" />
+ <meta name="Keywords" content="s6-dns skadnsd asynchronous dns daemon" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<a href="index.html">libskadns</a><br />
+<a href="../index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a><p />
+
+<h1> The <tt>skadnsd</tt> program </h1>
+
+<p>
+<tt>skadnsd</tt> is the skadns daemon. It reads a series of
+queries from the client on stdin, resolves them asynchronously,
+and writes
+the answers to the client as soon as it gets them. It exits 0
+when its stdin closes. It exits 111 on any serious error,
+writing the error message to stderr.
+</p>
+
+<p>
+<tt>skadnsd</tt> is a stub resolver. It reads <tt>/etc/resolv.conf</tt>
+at start looking for a "nameserver" line containing
+the address of a DNS cache (aka full resolver). It will exit 111 if it cannot
+find any valid cache address in <tt>/etc/resolv.conf</tt>. If the
+<tt>DNSCACHEIP</tt> environment variable is set, its value overrides
+what <tt>/etc/resolv.conf</tt> says.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+skadnsd does not fork, does not background itself automatically,
+and does not use syslog. It is not meant to be run directly by the
+user: it will be invoked and spawned by the skadns library calls.
+</p>
+
+<p>
+ There are 2 ways to use skadnsd:
+</p>
+<ol>
+<li> (preferred) Use the <tt>skadns_startf()</tt> library call.
+A <tt>skadnsd</tt> child will then be spawned from your
+calling process, and automatically reaped when you call
+<tt>skadns_end()</tt>. It requires care with applications that
+trap SIGCHLD. </li>
+<li> Use the <tt>skadns_start()</tt> library call, together with
+a <em>skadnsd service</em>. </li>
+</ol>
+
+<h3> Running skadnsd as a child process </h3>
+
+<p>
+ This is the simplest and safest way of using <em>skadns</em>. Forget
+about <tt>skadnsd</tt>: just start your library calls with
+<tt>skadns_startf()</tt> and end them with <tt>skadns_end()</tt>.
+Be careful though: if you're using SIGCHLD handlers, make sure they do
+not interfere with the child processes your application has without
+knowing. This is a general Unix programming rule.
+</p>
+
+<h3> Running a skadnsd as a daemon: the skadnsd service </h3>
+
+<p>
+ In this mode, you set up a daemon listening on a Unix domain socket,
+and clients connect to this socket to access the service. The
+advantage of this setup is that it works even with badly written
+clients that have trouble handling a child process; the drawback is
+that it requires support from the system administrator.
+</p>
+
+<p>
+skadnsd has no "standalone" mode: it is designed to work with a Unix
+domain superserver, like
+<a href="http://skarnet.org/software/s6-networking/s6-ipcserver.html">s6-ipcserver</a>.
+skadnsd follows the <a href="http://cr.yp.to/proto/ucspi.txt">UCSPI"</a>
+interface, it can be directly executed from the superserver.
+</p>
+
+<p>
+You should run skadnsd (and its Unix superserver) under a specific user
+and group, for elementary security reasons; and you should run its
+dedicated logger as another specific user. Do NOT run skadnsd as root;
+check your super-server documentation to find how
+to run it under a specific account.
+</p>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> Users should never invoke <tt>skadnsd</tt> directly. It's an
+internal program designed to be spawned by the skadns library. </li>
+ <li> If a poorly designed client sends a lot of queries and never reads the
+answers, those will indefinitely queue up in the daemon, eating up
+memory. You should run your process (or your Unix superserver, if you're
+using a skadnsd service) under a program like
+<a href="http://skarnet.org/software/s6/s6-softlimit.html">s6-softlimit</a> to
+set a memory limit to every skadnsd instance. If skadnsd runs out of
+allowed memory, it will simply die. </li>
+ <li> If you're using a skadnsd service: you should configure your
+Unix superserver to set a maximum
+number of concurrent skadnsd instances; most
+Unix superserver implementations provide the
+<tt>-c</tt> option for this. The s6-ipcserver program also
+allows you to specify a maximum number of concurrent daemon
+instances per client. </li>
+<li> The only way to ensure absolute reliability of the skadnsd process is
+to run it as a child, not as a daemon: use <tt>skadnsd_startf()</tt> when
+possible. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/upgrade.html b/doc/upgrade.html
new file mode 100644
index 0000000..c75d08e
--- /dev/null
+++ b/doc/upgrade.html
@@ -0,0 +1,32 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <title>s6-dns: how to upgrade</title>
+ <meta name="Description" content="s6-dns: how to upgrade" />
+ <meta name="Keywords" content="s6-dns installation upgrade" />
+ <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+ </head>
+<body>
+
+<p>
+<a href="index.html">s6-dns</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> What has changed in s6-dns </h1>
+
+<h2> in 2.0.0.0 </h2>
+
+<ul>
+ <li> The build system has completely changed. It is now a standard
+<tt>./configure &amp;&amp; make &amp;&amp; sudo make install</tt>
+build system. See the enclosed INSTALL file for details. </li>
+ <li> slashpackage is not activated by default. </li>
+ <li> shared libraries are not used by default. </li>
+ <li> skalibs dependency bumped to 2.0.0.0. </li>
+</ul>
+
+</body>
+</html>