Squashed 'third_party/rawrtc/re/' content from commit f3163ce8b

Change-Id: I6a235e6ac0f03269d951026f9d195da05c40fdab
git-subtree-dir: third_party/rawrtc/re
git-subtree-split: f3163ce8b526a13b35ef71ce4dd6f43585064d8a
diff --git a/src/dns/client.c b/src/dns/client.c
new file mode 100644
index 0000000..9535e07
--- /dev/null
+++ b/src/dns/client.c
@@ -0,0 +1,905 @@
+/**
+ * @file dns/client.c  DNS Client
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_udp.h>
+#include <re_tcp.h>
+#include <re_sys.h>
+#include <re_dns.h>
+
+
+#define DEBUG_MODULE "dnsc"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+enum {
+	NTX_MAX = 20,
+	QUERY_HASH_SIZE = 16,
+	TCP_HASH_SIZE = 2,
+	CONN_TIMEOUT = 10 * 1000,
+	IDLE_TIMEOUT = 30 * 1000,
+	SRVC_MAX = 32,
+};
+
+
+struct tcpconn {
+	struct le le;
+	struct list ql;
+	struct tmr tmr;
+	struct sa srv;
+	struct tcp_conn *conn;
+	struct mbuf *mb;
+	bool connected;
+	uint16_t flen;
+	struct dnsc *dnsc; /* parent */
+};
+
+
+struct dns_query {
+	struct le le;
+	struct le le_tc;
+	struct tmr tmr;
+	struct mbuf mb;
+	struct list rrlv[3];
+	char *name;
+	const struct sa *srvv;
+	const uint32_t *srvc;
+	struct tcpconn *tc;
+	struct dnsc *dnsc;     /* parent  */
+	struct dns_query **qp; /* app ref */
+	uint32_t ntx;
+	uint16_t id;
+	uint16_t type;
+	uint16_t dnsclass;
+	uint8_t opcode;
+	dns_query_h *qh;
+	void *arg;
+};
+
+
+struct dnsquery {
+	struct dnshdr hdr;
+	char *name;
+	uint16_t type;
+	uint16_t dnsclass;
+};
+
+
+struct dnsc {
+	struct dnsc_conf conf;
+	struct hash *ht_query;
+	struct hash *ht_tcpconn;
+	struct udp_sock *us;
+	struct sa srvv[SRVC_MAX];
+	uint32_t srvc;
+};
+
+
+static const struct dnsc_conf default_conf = {
+	QUERY_HASH_SIZE,
+	TCP_HASH_SIZE,
+	CONN_TIMEOUT,
+	IDLE_TIMEOUT,
+};
+
+
+static void tcpconn_close(struct tcpconn *tc, int err);
+static int  send_tcp(struct dns_query *q);
+static void udp_timeout_handler(void *arg);
+
+
+static bool rr_unlink_handler(struct le *le, void *arg)
+{
+	struct dnsrr *rr = le->data;
+	(void)arg;
+
+	list_unlink(&rr->le_priv);
+	mem_deref(rr);
+
+	return false;
+}
+
+
+static void query_abort(struct dns_query *q)
+{
+	if (q->tc) {
+		list_unlink(&q->le_tc);
+		q->tc = mem_deref(q->tc);
+	}
+
+	tmr_cancel(&q->tmr);
+	hash_unlink(&q->le);
+}
+
+
+static void query_destructor(void *data)
+{
+	struct dns_query *q = data;
+	uint32_t i;
+
+	query_abort(q);
+	mbuf_reset(&q->mb);
+	mem_deref(q->name);
+
+	for (i=0; i<ARRAY_SIZE(q->rrlv); i++)
+		(void)list_apply(&q->rrlv[i], true, rr_unlink_handler, NULL);
+}
+
+
+static void query_handler(struct dns_query *q, int err,
+			  const struct dnshdr *hdr, struct list *ansl,
+			  struct list *authl, struct list *addl)
+{
+	/* deref here - before calling handler */
+	if (q->qp)
+		*q->qp = NULL;
+
+	/* The handler must only be called _once_ */
+	if (q->qh) {
+		q->qh(err, hdr, ansl, authl, addl, q->arg);
+		q->qh = NULL;
+	}
+
+	/* in case we have more (than one) q refs */
+	query_abort(q);
+}
+
+
+static bool query_close_handler(struct le *le, void *arg)
+{
+	struct dns_query *q = le->data;
+	(void)arg;
+
+	query_handler(q, ECONNABORTED, NULL, NULL, NULL, NULL);
+	mem_deref(q);
+
+	return false;
+}
+
+
+static bool query_cmp_handler(struct le *le, void *arg)
+{
+	struct dns_query *q = le->data;
+	struct dnsquery *dq = arg;
+
+	if (q->id != dq->hdr.id)
+		return false;
+
+	if (q->opcode != dq->hdr.opcode)
+		return false;
+
+	if (q->type != dq->type)
+		return false;
+
+	if (q->dnsclass != dq->dnsclass)
+		return false;
+
+	if (str_casecmp(q->name, dq->name))
+		return false;
+
+	return true;
+}
+
+
+static int reply_recv(struct dnsc *dnsc, struct mbuf *mb)
+{
+	struct dns_query *q = NULL;
+	uint32_t i, j, nv[3];
+	struct dnsquery dq;
+	int err = 0;
+
+	if (!dnsc || !mb)
+		return EINVAL;
+
+	dq.name = NULL;
+
+	if (dns_hdr_decode(mb, &dq.hdr) || !dq.hdr.qr) {
+		err = EBADMSG;
+		goto out;
+	}
+
+	err = dns_dname_decode(mb, &dq.name, 0);
+	if (err)
+		goto out;
+
+	if (mbuf_get_left(mb) < 4) {
+		err = EBADMSG;
+		goto out;
+	}
+
+	dq.type     = ntohs(mbuf_read_u16(mb));
+	dq.dnsclass = ntohs(mbuf_read_u16(mb));
+
+	q = list_ledata(hash_lookup(dnsc->ht_query, hash_joaat_str_ci(dq.name),
+				    query_cmp_handler, &dq));
+	if (!q) {
+		err = ENOENT;
+		goto out;
+	}
+
+	/* try next server */
+	if (dq.hdr.rcode == DNS_RCODE_SRV_FAIL && q->ntx < *q->srvc) {
+
+		if (!q->tc) /* try next UDP server immediately */
+			tmr_start(&q->tmr, 0, udp_timeout_handler, q);
+
+		err = EPROTO;
+		goto out;
+	}
+
+	nv[0] = dq.hdr.nans;
+	nv[1] = dq.hdr.nauth;
+	nv[2] = dq.hdr.nadd;
+
+	for (i=0; i<ARRAY_SIZE(nv); i++) {
+
+		for (j=0; j<nv[i]; j++) {
+
+			struct dnsrr *rr = NULL;
+
+			err = dns_rr_decode(mb, &rr, 0);
+			if (err) {
+				query_handler(q, err, NULL, NULL, NULL, NULL);
+				mem_deref(q);
+				goto out;
+			}
+
+			list_append(&q->rrlv[i], &rr->le_priv, rr);
+		}
+	}
+
+	if (q->type == DNS_QTYPE_AXFR) {
+
+		struct dnsrr *rrh, *rrt;
+
+		rrh = list_ledata(list_head(&q->rrlv[0]));
+		rrt = list_ledata(list_tail(&q->rrlv[0]));
+
+		/* Wait for last AXFR reply with terminating SOA record */
+		if (dq.hdr.rcode == DNS_RCODE_OK && dq.hdr.nans > 0 &&
+		    (!rrt || rrt->type != DNS_TYPE_SOA || rrh == rrt)) {
+			DEBUG_INFO("waiting for last SOA record in reply\n");
+			goto out;
+		}
+	}
+
+	query_handler(q, 0, &dq.hdr, &q->rrlv[0], &q->rrlv[1], &q->rrlv[2]);
+	mem_deref(q);
+
+ out:
+	mem_deref(dq.name);
+
+	return err;
+}
+
+
+static void udp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg)
+{
+	(void)src;
+	(void)reply_recv(arg, mb);
+}
+
+
+static void tcp_recv_handler(struct mbuf *mbrx, void *arg)
+{
+	struct tcpconn *tc = arg;
+	struct mbuf *mb = tc->mb;
+	int err = 0;
+	size_t n;
+
+ next:
+	/* frame length */
+	if (!tc->flen) {
+
+		n = min(2 - mb->end, mbuf_get_left(mbrx));
+
+		err = mbuf_write_mem(mb, mbuf_buf(mbrx), n);
+		if (err)
+			goto error;
+
+		mbrx->pos += n;
+
+		if (mb->end < 2)
+			return;
+
+		mb->pos = 0;
+		tc->flen = ntohs(mbuf_read_u16(mb));
+		mb->pos = 0;
+		mb->end = 0;
+	}
+
+	/* content */
+	n = min(tc->flen - mb->end, mbuf_get_left(mbrx));
+
+	err = mbuf_write_mem(mb, mbuf_buf(mbrx), n);
+	if (err)
+		goto error;
+
+	mbrx->pos += n;
+
+	if (mb->end < tc->flen)
+		return;
+
+	mb->pos = 0;
+
+	err = reply_recv(tc->dnsc, mb);
+	if (err)
+		goto error;
+
+	/* reset tcp buffer */
+	tc->flen = 0;
+	mb->pos = 0;
+	mb->end = 0;
+
+	/* more data ? */
+	if (mbuf_get_left(mbrx) > 0) {
+		DEBUG_INFO("%u bytes of tcp data left\n", mbuf_get_left(mbrx));
+		goto next;
+	}
+
+	return;
+
+ error:
+	tcpconn_close(tc, err);
+}
+
+
+static void tcpconn_timeout_handler(void *arg)
+{
+	struct tcpconn *tc = arg;
+
+	DEBUG_NOTICE("tcp (%J) %s timeout \n", &tc->srv,
+		     tc->connected ? "idle" : "connect");
+
+	tcpconn_close(tc, ETIMEDOUT);
+}
+
+
+static void tcp_estab_handler(void *arg)
+{
+	struct tcpconn *tc = arg;
+	struct le *le = list_head(&tc->ql);
+	int err = 0;
+
+	DEBUG_INFO("connection (%J) established\n", &tc->srv);
+
+	while (le) {
+		struct dns_query *q = le->data;
+
+		le = le->next;
+
+		q->mb.pos = 0;
+		err = tcp_send(tc->conn, &q->mb);
+		if (err)
+			break;
+
+		DEBUG_INFO("tcp send %J\n", &tc->srv);
+	}
+
+	if (err) {
+		tcpconn_close(tc, err);
+		return;
+	}
+
+	tmr_start(&tc->tmr, tc->dnsc->conf.idle_timeout,
+		  tcpconn_timeout_handler, tc);
+	tc->connected = true;
+}
+
+
+static void tcp_close_handler(int err, void *arg)
+{
+	struct tcpconn *tc = arg;
+
+	DEBUG_NOTICE("connection (%J) closed: %m\n", &tc->srv, err);
+	tcpconn_close(tc, err);
+}
+
+
+static bool tcpconn_cmp_handler(struct le *le, void *arg)
+{
+	const struct tcpconn *tc = le->data;
+
+	/* avoid trying this connection if dead */
+	if (!tc->conn)
+		return false;
+
+	return sa_cmp(&tc->srv, arg, SA_ALL);
+}
+
+
+static bool tcpconn_fail_handler(struct le *le, void *arg)
+{
+	struct dns_query *q = le->data;
+	int err = *((int *)arg);
+
+	list_unlink(&q->le_tc);
+	q->tc = mem_deref(q->tc);
+
+	if (q->ntx >= *q->srvc) {
+		DEBUG_WARNING("all servers failed, giving up!!\n");
+		err = err ? err : ECONNREFUSED;
+		goto out;
+	}
+
+	/* try next server(s) */
+	err = send_tcp(q);
+	if (err) {
+		DEBUG_WARNING("all servers failed, giving up\n");
+		goto out;
+	}
+
+ out:
+	if (err) {
+		query_handler(q, err, NULL, NULL, NULL, NULL);
+		mem_deref(q);
+	}
+
+	return false;
+}
+
+
+static void tcpconn_close(struct tcpconn *tc, int err)
+{
+	if (!tc)
+		return;
+
+	/* avoid trying this connection again (e.g. same address) */
+	tc->conn = mem_deref(tc->conn);
+	(void)list_apply(&tc->ql, true, tcpconn_fail_handler, &err);
+	mem_deref(tc);
+}
+
+
+static void tcpconn_destructor(void *arg)
+{
+	struct tcpconn *tc = arg;
+
+	hash_unlink(&tc->le);
+	tmr_cancel(&tc->tmr);
+	mem_deref(tc->conn);
+	mem_deref(tc->mb);
+}
+
+
+static int tcpconn_alloc(struct tcpconn **tcpp, struct dnsc *dnsc,
+			 const struct sa *srv)
+{
+	struct tcpconn *tc;
+	int err = ENOMEM;
+
+	if (!tcpp || !dnsc || !srv)
+		return EINVAL;
+
+	tc = mem_zalloc(sizeof(struct tcpconn), tcpconn_destructor);
+	if (!tc)
+		goto out;
+
+	hash_append(dnsc->ht_tcpconn, sa_hash(srv, SA_ALL), &tc->le, tc);
+	tc->srv = *srv;
+	tc->dnsc = dnsc;
+
+	tc->mb = mbuf_alloc(1500);
+	if (!tc->mb)
+		goto out;
+
+	err = tcp_connect(&tc->conn, srv, tcp_estab_handler,
+			  tcp_recv_handler, tcp_close_handler, tc);
+	if (err)
+		goto out;
+
+	tmr_start(&tc->tmr, tc->dnsc->conf.conn_timeout,
+		  tcpconn_timeout_handler, tc);
+ out:
+	if (err)
+		mem_deref(tc);
+	else
+		*tcpp = tc;
+
+	return err;
+}
+
+
+static int send_tcp(struct dns_query *q)
+{
+	const struct sa *srv;
+	struct tcpconn *tc;
+	int err = 0;
+
+	if (!q)
+		return EINVAL;
+
+	while (q->ntx < *q->srvc) {
+
+		srv = &q->srvv[q->ntx++];
+
+		DEBUG_NOTICE("trying tcp server#%u: %J\n", q->ntx-1, srv);
+
+		tc = list_ledata(hash_lookup(q->dnsc->ht_tcpconn,
+					     sa_hash(srv, SA_ALL),
+					     tcpconn_cmp_handler,
+					     (void *)srv));
+		if (!tc) {
+			err = tcpconn_alloc(&tc, q->dnsc, srv);
+			if (err)
+				continue;
+		}
+
+		if (tc->connected) {
+			q->mb.pos = 0;
+			err = tcp_send(tc->conn, &q->mb);
+			if (err) {
+				tcpconn_close(tc, err);
+				continue;
+			}
+
+			tmr_start(&tc->tmr, tc->dnsc->conf.idle_timeout,
+				  tcpconn_timeout_handler, tc);
+			DEBUG_NOTICE("tcp send %J\n", srv);
+		}
+
+		list_append(&tc->ql, &q->le_tc, q);
+		q->tc = mem_ref(tc);
+		break;
+	}
+
+	return err;
+}
+
+
+static void tcp_timeout_handler(void *arg)
+{
+	struct dns_query *q = arg;
+
+	query_handler(q, ETIMEDOUT, NULL, NULL, NULL, NULL);
+	mem_deref(q);
+}
+
+
+static int send_udp(struct dns_query *q)
+{
+	const struct sa *srv;
+	int err = ETIMEDOUT;
+	uint32_t i;
+
+	if (!q)
+		return EINVAL;
+
+	for (i=0; i<*q->srvc; i++) {
+
+		srv = &q->srvv[q->ntx++%*q->srvc];
+
+		DEBUG_INFO("trying udp server#%u: %J\n", i, srv);
+
+		q->mb.pos = 0;
+		err = udp_send(q->dnsc->us, srv, &q->mb);
+		if (!err)
+			break;
+	}
+
+	return err;
+}
+
+
+static void udp_timeout_handler(void *arg)
+{
+	struct dns_query *q = arg;
+	int err = ETIMEDOUT;
+
+	if (q->ntx >= NTX_MAX)
+		goto out;
+
+	err = send_udp(q);
+	if (err)
+		goto out;
+
+	tmr_start(&q->tmr, 1000<<MIN(2, q->ntx - 2),
+		  udp_timeout_handler, q);
+
+ out:
+	if (err) {
+		query_handler(q, err, NULL, NULL, NULL, NULL);
+		mem_deref(q);
+	}
+}
+
+
+static int query(struct dns_query **qp, struct dnsc *dnsc, uint8_t opcode,
+		 const char *name, uint16_t type, uint16_t dnsclass,
+		 const struct dnsrr *ans_rr, int proto,
+		 const struct sa *srvv, const uint32_t *srvc,
+		 bool aa, bool rd, dns_query_h *qh, void *arg)
+{
+	struct dns_query *q = NULL;
+	struct dnshdr hdr;
+	int err = 0;
+	uint32_t i;
+
+	if (!dnsc || !name || !srvv || !srvc || !(*srvc))
+		return EINVAL;
+
+	if (DNS_QTYPE_AXFR == type)
+		proto = IPPROTO_TCP;
+
+	q = mem_zalloc(sizeof(*q), query_destructor);
+	if (!q)
+		goto nmerr;
+
+	hash_append(dnsc->ht_query, hash_joaat_str_ci(name), &q->le, q);
+	tmr_init(&q->tmr);
+	mbuf_init(&q->mb);
+
+	for (i=0; i<ARRAY_SIZE(q->rrlv); i++)
+		list_init(&q->rrlv[i]);
+
+	err = str_dup(&q->name, name);
+	if (err)
+		goto error;
+
+	q->srvv = srvv;
+	q->srvc = srvc;
+	q->id   = rand_u16();
+	q->type = type;
+	q->opcode = opcode;
+	q->dnsclass = dnsclass;
+	q->dnsc = dnsc;
+
+	memset(&hdr, 0, sizeof(hdr));
+
+	hdr.id = q->id;
+	hdr.opcode = q->opcode;
+	hdr.aa = aa;
+	hdr.rd = rd;
+	hdr.nq = 1;
+	hdr.nans = ans_rr ? 1 : 0;
+
+	if (proto == IPPROTO_TCP)
+		q->mb.pos += 2;
+
+	err = dns_hdr_encode(&q->mb, &hdr);
+	if (err)
+		goto error;
+
+	err = dns_dname_encode(&q->mb, name, NULL, 0, false);
+	if (err)
+		goto error;
+
+	err |= mbuf_write_u16(&q->mb, htons(type));
+	err |= mbuf_write_u16(&q->mb, htons(dnsclass));
+	if (err)
+		goto error;
+
+	if (ans_rr) {
+		err = dns_rr_encode(&q->mb, ans_rr, 0, NULL, 0);
+		if (err)
+			goto error;
+	}
+
+	q->qh  = qh;
+	q->arg = arg;
+
+	switch (proto) {
+
+	case IPPROTO_TCP:
+		q->mb.pos = 0;
+		(void)mbuf_write_u16(&q->mb, htons(q->mb.end - 2));
+
+		err = send_tcp(q);
+		if (err)
+			goto error;
+
+		tmr_start(&q->tmr, 60 * 1000, tcp_timeout_handler, q);
+		break;
+
+	case IPPROTO_UDP:
+		err = send_udp(q);
+		if (err)
+			goto error;
+
+		tmr_start(&q->tmr, 500, udp_timeout_handler, q);
+		break;
+
+	default:
+		err = EPROTONOSUPPORT;
+		goto error;
+	}
+
+	if (qp) {
+		q->qp = qp;
+		*qp = q;
+	}
+
+	return 0;
+
+ nmerr:
+	err = ENOMEM;
+ error:
+	mem_deref(q);
+
+	return err;
+}
+
+
+/**
+ * Query a DNS name
+ *
+ * @param qp       Pointer to allocated DNS query
+ * @param dnsc     DNS Client
+ * @param name     DNS name
+ * @param type     DNS Resource Record type
+ * @param dnsclass DNS Class
+ * @param rd       Recursion Desired (RD) flag
+ * @param qh       Query handler
+ * @param arg      Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dnsc_query(struct dns_query **qp, struct dnsc *dnsc, const char *name,
+	       uint16_t type, uint16_t dnsclass,
+	       bool rd, dns_query_h *qh, void *arg)
+{
+	if (!dnsc)
+		return EINVAL;
+
+	return query(qp, dnsc, DNS_OPCODE_QUERY, name, type, dnsclass, NULL,
+		     IPPROTO_UDP, dnsc->srvv, &dnsc->srvc, false, rd, qh, arg);
+}
+
+
+/**
+ * Query a DNS name SRV record
+ *
+ * @param qp       Pointer to allocated DNS query
+ * @param dnsc     DNS Client
+ * @param name     DNS name
+ * @param type     DNS Resource Record type
+ * @param dnsclass DNS Class
+ * @param proto    Protocol
+ * @param srvv     DNS Nameservers
+ * @param srvc     Number of DNS nameservers
+ * @param rd       Recursion Desired (RD) flag
+ * @param qh       Query handler
+ * @param arg      Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dnsc_query_srv(struct dns_query **qp, struct dnsc *dnsc, const char *name,
+		   uint16_t type, uint16_t dnsclass, int proto,
+		   const struct sa *srvv, const uint32_t *srvc,
+		   bool rd, dns_query_h *qh, void *arg)
+{
+	return query(qp, dnsc, DNS_OPCODE_QUERY, name, type, dnsclass,
+		     NULL, proto, srvv, srvc, false, rd, qh, arg);
+}
+
+
+/**
+ * Send a DNS query with NOTIFY opcode
+ *
+ * @param qp       Pointer to allocated DNS query
+ * @param dnsc     DNS Client
+ * @param name     DNS name
+ * @param type     DNS Resource Record type
+ * @param dnsclass DNS Class
+ * @param ans_rr   Answer Resource Record
+ * @param proto    Protocol
+ * @param srvv     DNS Nameservers
+ * @param srvc     Number of DNS nameservers
+ * @param qh       Query handler
+ * @param arg      Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dnsc_notify(struct dns_query **qp, struct dnsc *dnsc, const char *name,
+		uint16_t type, uint16_t dnsclass, const struct dnsrr *ans_rr,
+		int proto, const struct sa *srvv, const uint32_t *srvc,
+		dns_query_h *qh, void *arg)
+{
+	return query(qp, dnsc, DNS_OPCODE_NOTIFY, name, type, dnsclass,
+		     ans_rr, proto, srvv, srvc, true, false, qh, arg);
+}
+
+
+static void dnsc_destructor(void *data)
+{
+	struct dnsc *dnsc = data;
+
+	(void)hash_apply(dnsc->ht_query, query_close_handler, NULL);
+	hash_flush(dnsc->ht_tcpconn);
+
+	mem_deref(dnsc->ht_tcpconn);
+	mem_deref(dnsc->ht_query);
+	mem_deref(dnsc->us);
+}
+
+
+/**
+ * Allocate a DNS Client
+ *
+ * @param dcpp Pointer to allocated DNS Client
+ * @param conf Optional DNS configuration, NULL for default
+ * @param srvv DNS servers
+ * @param srvc Number of DNS Servers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dnsc_alloc(struct dnsc **dcpp, const struct dnsc_conf *conf,
+	       const struct sa *srvv, uint32_t srvc)
+{
+	struct dnsc *dnsc;
+	int err;
+
+	if (!dcpp)
+		return EINVAL;
+
+	dnsc = mem_zalloc(sizeof(*dnsc), dnsc_destructor);
+	if (!dnsc)
+		return ENOMEM;
+
+	if (conf)
+		dnsc->conf = *conf;
+	else
+		dnsc->conf = default_conf;
+
+	err = dnsc_srv_set(dnsc, srvv, srvc);
+	if (err)
+		goto out;
+
+	err = udp_listen(&dnsc->us, NULL, udp_recv_handler, dnsc);
+	if (err)
+		goto out;
+
+	err = hash_alloc(&dnsc->ht_query, dnsc->conf.query_hash_size);
+	if (err)
+		goto out;
+
+	err = hash_alloc(&dnsc->ht_tcpconn, dnsc->conf.tcp_hash_size);
+	if (err)
+		goto out;
+
+ out:
+	if (err)
+		mem_deref(dnsc);
+	else
+		*dcpp = dnsc;
+
+	return err;
+}
+
+
+/**
+ * Set the DNS Servers on a DNS Client
+ *
+ * @param dnsc DNS Client
+ * @param srvv DNS Nameservers
+ * @param srvc Number of nameservers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dnsc_srv_set(struct dnsc *dnsc, const struct sa *srvv, uint32_t srvc)
+{
+	uint32_t i;
+
+	if (!dnsc)
+		return EINVAL;
+
+	dnsc->srvc = min((uint32_t)ARRAY_SIZE(dnsc->srvv), srvc);
+
+	if (srvv) {
+		for (i=0; i<dnsc->srvc; i++)
+			dnsc->srvv[i] = srvv[i];
+	}
+
+	return 0;
+}
diff --git a/src/dns/cstr.c b/src/dns/cstr.c
new file mode 100644
index 0000000..9e6c90a
--- /dev/null
+++ b/src/dns/cstr.c
@@ -0,0 +1,61 @@
+/**
+ * @file cstr.c  DNS character strings encoding
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_dns.h>
+
+
+/**
+ * Encode a DNS character string into a memory buffer
+ *
+ * @param mb  Memory buffer to encode into
+ * @param str Character string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_cstr_encode(struct mbuf *mb, const char *str)
+{
+	uint8_t len;
+	int err = 0;
+
+	if (!mb || !str)
+		return EINVAL;
+
+	len = (uint8_t)strlen(str);
+
+	err |= mbuf_write_u8(mb, len);
+	err |= mbuf_write_mem(mb, (const uint8_t *)str, len);
+
+	return err;
+}
+
+
+/**
+ * Decode a DNS character string from a memory buffer
+ *
+ * @param mb  Memory buffer to decode from
+ * @param str Pointer to allocated character string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_cstr_decode(struct mbuf *mb, char **str)
+{
+	uint8_t len;
+
+	if (!mb || !str || (mbuf_get_left(mb) < 1))
+		return EINVAL;
+
+	len = mbuf_read_u8(mb);
+
+	if (mbuf_get_left(mb) < len)
+		return EBADMSG;
+
+	return mbuf_strdup(mb, str, len);
+}
diff --git a/src/dns/darwin/srv.c b/src/dns/darwin/srv.c
new file mode 100644
index 0000000..05c92c1
--- /dev/null
+++ b/src/dns/darwin/srv.c
@@ -0,0 +1,83 @@
+/**
+ * @file darwin/srv.c  Get DNS Server IP code for Mac OS X
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_dns.h>
+#include "../dns.h"
+#define __CF_USE_FRAMEWORK_INCLUDES__
+#include <SystemConfiguration/SystemConfiguration.h>
+
+
+int get_darwin_dns(char *domain, size_t dsize, struct sa *nsv, uint32_t *n)
+{
+#if TARGET_OS_IPHONE
+	(void)domain;
+	(void)dsize;
+	(void)nsv;
+	(void)n;
+	return ENOSYS;
+#else
+	SCDynamicStoreContext context = {0, NULL, NULL, NULL, NULL};
+	CFArrayRef addresses, domains;
+	SCDynamicStoreRef store;
+	CFStringRef key, dom;
+	CFDictionaryRef dict;
+	uint32_t c, i;
+	int err = ENOENT;
+
+	if (!nsv || !n)
+		return EINVAL;
+
+	store = SCDynamicStoreCreate(NULL, CFSTR("get_darwin_dns"),
+				     NULL, &context);
+	if (!store)
+		return ENOENT;
+
+	key = CFSTR("State:/Network/Global/DNS");
+	dict = SCDynamicStoreCopyValue(store, key);
+	if (!dict)
+		goto out1;
+
+	addresses = CFDictionaryGetValue(dict, kSCPropNetDNSServerAddresses);
+	if (!addresses)
+		goto out;
+
+	c = (uint32_t)CFArrayGetCount(addresses);
+	*n = min(*n, c);
+
+	for (i=0; i<*n; i++) {
+		CFStringRef address = CFArrayGetValueAtIndex(addresses, i);
+		char str[64];
+
+		CFStringGetCString(address, str, sizeof(str),
+				   kCFStringEncodingUTF8);
+
+		err = sa_set_str(&nsv[i], str, DNS_PORT);
+		if (err)
+			break;
+	}
+
+	domains = CFDictionaryGetValue(dict, kSCPropNetDNSSearchDomains);
+	if (!domains)
+		goto out;
+
+	if (CFArrayGetCount(domains) < 1)
+		goto out;
+
+	dom = CFArrayGetValueAtIndex(domains, 0);
+	CFStringGetCString(dom, domain, dsize, kCFStringEncodingUTF8);
+
+ out:
+	CFRelease(dict);
+ out1:
+	CFRelease(store);
+
+	return err;
+#endif
+}
diff --git a/src/dns/dname.c b/src/dns/dname.c
new file mode 100644
index 0000000..eda9ba4
--- /dev/null
+++ b/src/dns/dname.c
@@ -0,0 +1,219 @@
+/**
+ * @file dname.c  DNS domain names
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_net.h>
+#include <re_dns.h>
+
+
+#define COMP_MASK   0xc0
+#define OFFSET_MASK 0x3fff
+#define COMP_LOOP   255
+
+
+struct dname {
+	struct le he;
+	size_t pos;
+	char *name;
+};
+
+
+static void destructor(void *arg)
+{
+	struct dname *dn = arg;
+
+	hash_unlink(&dn->he);
+	mem_deref(dn->name);
+}
+
+
+static void dname_append(struct hash *ht_dname, const char *name, size_t pos)
+{
+	struct dname *dn;
+
+	if (!ht_dname || pos > OFFSET_MASK || !*name)
+		return;
+
+	dn = mem_zalloc(sizeof(*dn), destructor);
+	if (!dn)
+		return;
+
+	if (str_dup(&dn->name, name)) {
+		mem_deref(dn);
+		return;
+	}
+
+	hash_append(ht_dname, hash_joaat_str_ci(name), &dn->he, dn);
+	dn->pos = pos;
+}
+
+
+static bool lookup_handler(struct le *le, void *arg)
+{
+	struct dname *dn = le->data;
+
+	return 0 == str_casecmp(dn->name, arg);
+}
+
+
+static inline struct dname *dname_lookup(struct hash *ht_dname,
+					 const char *name)
+{
+	return list_ledata(hash_lookup(ht_dname, hash_joaat_str_ci(name),
+				       lookup_handler, (void *)name));
+}
+
+
+static inline int dname_encode_pointer(struct mbuf *mb, size_t pos)
+{
+	return mbuf_write_u16(mb, htons(pos | (COMP_MASK<<8)));
+}
+
+
+/**
+ * Encode a DNS Domain name into a memory buffer
+ *
+ * @param mb       Memory buffer
+ * @param name     Domain name
+ * @param ht_dname Domain name hashtable
+ * @param start    Start position
+ * @param comp     Enable compression
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_dname_encode(struct mbuf *mb, const char *name,
+		     struct hash *ht_dname, size_t start, bool comp)
+{
+	struct dname *dn;
+	size_t pos;
+	int err;
+
+	if (!mb || !name)
+		return EINVAL;
+
+	dn = dname_lookup(ht_dname, name);
+	if (dn && comp)
+		return dname_encode_pointer(mb, dn->pos);
+
+	pos = mb->pos;
+	if (!dn)
+		dname_append(ht_dname, name, pos - start);
+	err = mbuf_write_u8(mb, 0);
+
+	if ('.' == name[0] && '\0' == name[1])
+		return err;
+
+	while (err == 0) {
+
+		const size_t lablen = mb->pos - pos - 1;
+
+		if ('\0' == *name) {
+			if (!lablen)
+				break;
+
+			mb->buf[pos] = lablen;
+			err |= mbuf_write_u8(mb, 0);
+			break;
+		}
+		else if ('.' == *name) {
+			if (!lablen)
+				return EINVAL;
+
+			mb->buf[pos] = lablen;
+
+			dn = dname_lookup(ht_dname, name + 1);
+			if (dn && comp) {
+				err |= dname_encode_pointer(mb, dn->pos);
+				break;
+			}
+
+			pos = mb->pos;
+			if (!dn)
+				dname_append(ht_dname, name + 1, pos - start);
+			err |= mbuf_write_u8(mb, 0);
+		}
+		else {
+			err |= mbuf_write_u8(mb, *name);
+		}
+
+		++name;
+	}
+
+	return err;
+}
+
+
+/**
+ * Decode a DNS domain name from a memory buffer
+ *
+ * @param mb    Memory buffer to decode from
+ * @param name  Pointer to allocated string with domain name
+ * @param start Start position
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_dname_decode(struct mbuf *mb, char **name, size_t start)
+{
+	uint32_t i = 0, loopc = 0;
+	bool comp = false;
+	size_t pos = 0;
+	char buf[256];
+
+	if (!mb || !name)
+		return EINVAL;
+
+	while (mb->pos < mb->end) {
+
+		uint8_t len = mb->buf[mb->pos++];
+		if (!len) {
+			if (comp)
+				mb->pos = pos;
+
+			buf[i++] = '\0';
+
+			*name = mem_alloc(i, NULL);
+			if (!*name)
+				return ENOMEM;
+
+			str_ncpy(*name, buf, i);
+
+			return 0;
+		}
+		else if ((len & COMP_MASK) == COMP_MASK) {
+			uint16_t offset;
+
+			if (loopc++ > COMP_LOOP)
+				break;
+
+			--mb->pos;
+
+			offset = ntohs(mbuf_read_u16(mb)) & OFFSET_MASK;
+			if (!comp) {
+				pos  = mb->pos;
+				comp = true;
+			}
+
+			mb->pos = offset + start;
+			continue;
+		}
+		else if (len > mbuf_get_left(mb))
+			break;
+		else if (len > sizeof(buf) - i - 2)
+			break;
+
+		if (i > 0)
+			buf[i++] = '.';
+
+		while (len--)
+			buf[i++] = mb->buf[mb->pos++];
+	}
+
+	return EINVAL;
+}
diff --git a/src/dns/dns.h b/src/dns/dns.h
new file mode 100644
index 0000000..b2c58e8
--- /dev/null
+++ b/src/dns/dns.h
@@ -0,0 +1,16 @@
+/**
+ * @file dns.h  Internal DNS header file
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#ifdef HAVE_RESOLV
+int get_resolv_dns(char *domain, size_t dsize, struct sa *nsv, uint32_t *n);
+#endif
+#ifdef WIN32
+int get_windns(char *domain, size_t dsize, struct sa *nav, uint32_t *n);
+#endif
+#ifdef DARWIN
+int get_darwin_dns(char *domain, size_t dsize, struct sa *nsv, uint32_t *n);
+#endif
diff --git a/src/dns/hdr.c b/src/dns/hdr.c
new file mode 100644
index 0000000..5133b33
--- /dev/null
+++ b/src/dns/hdr.c
@@ -0,0 +1,137 @@
+/**
+ * @file dns/hdr.c  DNS header encoding
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_mbuf.h>
+#include <re_net.h>
+#include <re_dns.h>
+
+
+enum {
+	QUERY_RESPONSE      = 15,
+	OPCODE              = 11,
+	AUTH_ANSWER         = 10,
+	TRUNCATED           =  9,
+	RECURSION_DESIRED   =  8,
+	RECURSION_AVAILABLE =  7,
+	ZERO                =  4
+};
+
+
+/**
+ * Encode a DNS header
+ *
+ * @param mb  Memory buffer to encode header into
+ * @param hdr DNS header
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_hdr_encode(struct mbuf *mb, const struct dnshdr *hdr)
+{
+	uint16_t flags = 0;
+	int err = 0;
+
+	if (!mb || !hdr)
+		return EINVAL;
+
+	flags |= hdr->qr     <<QUERY_RESPONSE;
+	flags |= hdr->opcode <<OPCODE;
+	flags |= hdr->aa     <<AUTH_ANSWER;
+	flags |= hdr->tc     <<TRUNCATED;
+	flags |= hdr->rd     <<RECURSION_DESIRED;
+	flags |= hdr->ra     <<RECURSION_AVAILABLE;
+	flags |= hdr->z      <<ZERO;
+	flags |= hdr->rcode;
+
+	err |= mbuf_write_u16(mb, htons(hdr->id));
+	err |= mbuf_write_u16(mb, htons(flags));
+	err |= mbuf_write_u16(mb, htons(hdr->nq));
+	err |= mbuf_write_u16(mb, htons(hdr->nans));
+	err |= mbuf_write_u16(mb, htons(hdr->nauth));
+	err |= mbuf_write_u16(mb, htons(hdr->nadd));
+
+	return err;
+}
+
+
+/**
+ * Decode a DNS header from a memory buffer
+ *
+ * @param mb  Memory buffer to decode header from
+ * @param hdr DNS header (output)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_hdr_decode(struct mbuf *mb, struct dnshdr *hdr)
+{
+	uint16_t flags = 0;
+
+	if (!mb || !hdr || (mbuf_get_left(mb) < DNS_HEADER_SIZE))
+		return EINVAL;
+
+	hdr->id = ntohs(mbuf_read_u16(mb));
+	flags   = ntohs(mbuf_read_u16(mb));
+
+	hdr->qr     = 0x1 & (flags >> QUERY_RESPONSE);
+	hdr->opcode = 0xf & (flags >> OPCODE);
+	hdr->aa     = 0x1 & (flags >> AUTH_ANSWER);
+	hdr->tc     = 0x1 & (flags >> TRUNCATED);
+	hdr->rd     = 0x1 & (flags >> RECURSION_DESIRED);
+	hdr->ra     = 0x1 & (flags >> RECURSION_AVAILABLE);
+	hdr->z      = 0x7 & (flags >> ZERO);
+	hdr->rcode  = 0xf & (flags >> 0);
+
+	hdr->nq    = ntohs(mbuf_read_u16(mb));
+	hdr->nans  = ntohs(mbuf_read_u16(mb));
+	hdr->nauth = ntohs(mbuf_read_u16(mb));
+	hdr->nadd  = ntohs(mbuf_read_u16(mb));
+
+	return 0;
+}
+
+
+/**
+ * Get the string of a DNS opcode
+ *
+ * @param opcode DNS opcode
+ *
+ * @return Opcode string
+ */
+const char *dns_hdr_opcodename(uint8_t opcode)
+{
+	switch (opcode) {
+
+	case DNS_OPCODE_QUERY:  return "QUERY";
+	case DNS_OPCODE_IQUERY: return "IQUERY";
+	case DNS_OPCODE_STATUS: return "STATUS";
+	case DNS_OPCODE_NOTIFY: return "NOTIFY";
+	default:                return "??";
+	}
+}
+
+
+/**
+ * Get the string of a DNS response code
+ *
+ * @param rcode Response code
+ *
+ * @return Response code string
+ */
+const char *dns_hdr_rcodename(uint8_t rcode)
+{
+	switch (rcode) {
+
+	case DNS_RCODE_OK:       return "OK";
+	case DNS_RCODE_FMT_ERR:  return "Format Error";
+	case DNS_RCODE_SRV_FAIL: return "Server Failure";
+	case DNS_RCODE_NAME_ERR: return "Name Error";
+	case DNS_RCODE_NOT_IMPL: return "Not Implemented";
+	case DNS_RCODE_REFUSED:  return "Refused";
+	case DNS_RCODE_NOT_AUTH: return "Server Not Authoritative for zone";
+	default:                 return "??";
+	}
+}
diff --git a/src/dns/mod.mk b/src/dns/mod.mk
new file mode 100644
index 0000000..1137b61
--- /dev/null
+++ b/src/dns/mod.mk
@@ -0,0 +1,27 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS	+= dns/client.c
+SRCS	+= dns/cstr.c
+SRCS	+= dns/dname.c
+SRCS	+= dns/hdr.c
+SRCS	+= dns/ns.c
+SRCS	+= dns/rr.c
+SRCS	+= dns/rrlist.c
+
+ifneq ($(HAVE_RESOLV),)
+SRCS	+= dns/res.c
+endif
+
+ifeq ($(OS),win32)
+SRCS	+= dns/win32/srv.c
+endif
+
+ifeq ($(OS),darwin)
+SRCS	+= dns/darwin/srv.c
+# add libraries for darwin dns servers
+LFLAGS	+= -framework SystemConfiguration -framework CoreFoundation
+endif
diff --git a/src/dns/ns.c b/src/dns/ns.c
new file mode 100644
index 0000000..aac713d
--- /dev/null
+++ b/src/dns/ns.c
@@ -0,0 +1,150 @@
+/**
+ * @file ns.c  DNS Nameserver configuration
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdio.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_dns.h>
+#include "dns.h"
+#ifdef __ANDROID__
+#include <sys/system_properties.h>
+#endif
+
+
+#define DEBUG_MODULE "ns"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+static int parse_resolv_conf(char *domain, size_t dsize,
+			     struct sa *srvv, uint32_t *n)
+{
+	FILE *f;
+	struct pl dom = pl_null;
+	uint32_t i = 0;
+	int err = 0;
+
+	if (!srvv || !n || !*n)
+		return EINVAL;
+
+	f = fopen("/etc/resolv.conf", "r");
+	if (!f)
+		return errno;
+
+	for (;;) {
+		char line[128];
+		struct pl srv;
+		size_t len;
+
+		if (1 != fscanf(f, "%127[^\n]\n", line))
+			break;
+
+		if ('#' == line[0])
+			continue;
+
+		len = str_len(line);
+
+		/* Set domain if not already set */
+		if (!pl_isset(&dom)) {
+			if (0 == re_regex(line, len, "domain [^ ]+", &dom)) {
+				(void)pl_strcpy(&dom, domain, dsize);
+			}
+
+			if (0 == re_regex(line, len, "search [^ ]+", &dom)) {
+				(void)pl_strcpy(&dom, domain, dsize);
+			}
+		}
+
+		/* Use the first entry */
+		if (i < *n && 0 == re_regex(line, len, "nameserver [^\n]+",
+					    &srv)) {
+			err = sa_set(&srvv[i], &srv, DNS_PORT);
+			if (err) {
+				DEBUG_WARNING("sa_set: %r (%m)\n", &srv, err);
+			}
+			++i;
+		}
+	}
+
+	*n = i;
+
+	(void)fclose(f);
+
+	return err;
+}
+
+
+#ifdef __ANDROID__
+static int get_android_dns(struct sa *nsv, uint32_t *n)
+{
+	char prop[PROP_NAME_MAX] = {0}, value[PROP_VALUE_MAX] = {0};
+	uint32_t i, count = 0;
+	int err;
+
+	for (i=0; i<*n; i++) {
+		re_snprintf(prop, sizeof(prop), "net.dns%u", 1+i);
+
+		if (__system_property_get(prop, value)) {
+
+			err = sa_set_str(&nsv[count], value, DNS_PORT);
+			if (!err)
+				++count;
+		}
+	}
+	if (count == 0)
+		return ENOENT;
+
+	*n = count;
+
+	return 0;
+}
+#endif
+
+
+/**
+ * Get the DNS domain and nameservers
+ *
+ * @param domain Returned domain name
+ * @param dsize  Size of domain name buffer
+ * @param srvv   Returned nameservers
+ * @param n      Nameservers capacity, actual on return
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_srv_get(char *domain, size_t dsize, struct sa *srvv, uint32_t *n)
+{
+	int err;
+
+	/* Try them all in prioritized order */
+
+#ifdef HAVE_RESOLV
+	err = get_resolv_dns(domain, dsize, srvv, n);
+	if (!err)
+		return 0;
+#endif
+
+#ifdef DARWIN
+	err = get_darwin_dns(domain, dsize, srvv, n);
+	if (!err)
+		return 0;
+#endif
+
+	err = parse_resolv_conf(domain, dsize, srvv, n);
+	if (!err)
+		return 0;
+
+#ifdef WIN32
+	err = get_windns(domain, dsize, srvv, n);
+#endif
+
+#ifdef __ANDROID__
+	err = get_android_dns(srvv, n);
+#endif
+
+	return err;
+}
diff --git a/src/dns/res.c b/src/dns/res.c
new file mode 100644
index 0000000..7df3d46
--- /dev/null
+++ b/src/dns/res.c
@@ -0,0 +1,65 @@
+/**
+ * @file res.c  Get DNS Server IP using resolv
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _BSD_SOURCE 1
+#define _DEFAULT_SOURCE 1
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_dns.h>
+#include "dns.h"
+
+
+int get_resolv_dns(char *domain, size_t dsize, struct sa *nsv, uint32_t *n)
+{
+	struct __res_state state;
+	uint32_t i;
+	int ret, err;
+
+#ifdef OPENBSD
+	ret = res_init();
+	state = _res;
+#else
+	memset(&state, 0, sizeof(state));
+	ret = res_ninit(&state);
+#endif
+	if (0 != ret)
+		return ENOENT;
+
+	if (state.dnsrch[0])
+		str_ncpy(domain, state.dnsrch[0], dsize);
+	else if ((char *)state.defdname)
+		str_ncpy(domain, state.defdname, dsize);
+
+	if (!state.nscount) {
+		err = ENOENT;
+		goto out;
+	}
+
+	err = 0;
+	for (i=0; i<min(*n, (uint32_t)state.nscount) && !err; i++) {
+		struct sockaddr_in *addr = &state.nsaddr_list[i];
+		err |= sa_set_sa(&nsv[i], (struct sockaddr *)addr);
+	}
+	if (err)
+		goto out;
+
+	*n = i;
+
+ out:
+#ifdef OPENBSD
+#else
+	res_nclose(&state);
+#endif
+
+	return err;
+}
diff --git a/src/dns/rr.c b/src/dns/rr.c
new file mode 100644
index 0000000..cb06d5d
--- /dev/null
+++ b/src/dns/rr.c
@@ -0,0 +1,630 @@
+/**
+ * @file dns/rr.c  DNS Resource Records
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_net.h>
+#include <re_sa.h>
+#include <re_dns.h>
+
+
+static void rr_destructor(void *data)
+{
+	struct dnsrr *rr = data;
+
+	mem_deref(rr->name);
+
+	switch (rr->type) {
+
+	case DNS_TYPE_NS:
+		mem_deref(rr->rdata.ns.nsdname);
+		break;
+
+	case DNS_TYPE_CNAME:
+		mem_deref(rr->rdata.cname.cname);
+		break;
+
+	case DNS_TYPE_SOA:
+		mem_deref(rr->rdata.soa.mname);
+		mem_deref(rr->rdata.soa.rname);
+		break;
+
+	case DNS_TYPE_PTR:
+		mem_deref(rr->rdata.ptr.ptrdname);
+		break;
+
+	case DNS_TYPE_MX:
+		mem_deref(rr->rdata.mx.exchange);
+		break;
+
+	case DNS_TYPE_SRV:
+		mem_deref(rr->rdata.srv.target);
+		break;
+
+	case DNS_TYPE_NAPTR:
+		mem_deref(rr->rdata.naptr.flags);
+		mem_deref(rr->rdata.naptr.services);
+		mem_deref(rr->rdata.naptr.regexp);
+		mem_deref(rr->rdata.naptr.replace);
+		break;
+	}
+}
+
+
+/**
+ * Allocate a new DNS Resource Record (RR)
+ *
+ * @return Newly allocated Resource Record, or NULL if no memory
+ */
+struct dnsrr *dns_rr_alloc(void)
+{
+	return mem_zalloc(sizeof(struct dnsrr), rr_destructor);
+}
+
+
+/**
+ * Encode a DNS Resource Record
+ *
+ * @param mb       Memory buffer to encode into
+ * @param rr       DNS Resource Record
+ * @param ttl_offs TTL Offset
+ * @param ht_dname Domain name hash-table
+ * @param start    Start position
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_rr_encode(struct mbuf *mb, const struct dnsrr *rr, int64_t ttl_offs,
+		  struct hash *ht_dname, size_t start)
+{
+	uint32_t ttl;
+	uint16_t len;
+	size_t start_rdata;
+	int err = 0;
+
+	if (!mb || !rr)
+		return EINVAL;
+
+	ttl = (uint32_t)((rr->ttl > ttl_offs) ? (rr->ttl - ttl_offs) : 0);
+
+	err |= dns_dname_encode(mb, rr->name, ht_dname, start, true);
+	err |= mbuf_write_u16(mb, htons(rr->type));
+	err |= mbuf_write_u16(mb, htons(rr->dnsclass));
+	err |= mbuf_write_u32(mb, htonl(ttl));
+	err |= mbuf_write_u16(mb, htons(rr->rdlen));
+
+	start_rdata = mb->pos;
+
+	switch (rr->type) {
+
+	case DNS_TYPE_A:
+		err |= mbuf_write_u32(mb, htonl(rr->rdata.a.addr));
+		break;
+
+	case DNS_TYPE_NS:
+		err |= dns_dname_encode(mb, rr->rdata.ns.nsdname,
+					ht_dname, start, true);
+		break;
+
+	case DNS_TYPE_CNAME:
+		err |= dns_dname_encode(mb, rr->rdata.cname.cname,
+					ht_dname, start, true);
+		break;
+
+	case DNS_TYPE_SOA:
+		err |= dns_dname_encode(mb, rr->rdata.soa.mname,
+					ht_dname, start, true);
+		err |= dns_dname_encode(mb, rr->rdata.soa.rname,
+					ht_dname, start, true);
+		err |= mbuf_write_u32(mb, htonl(rr->rdata.soa.serial));
+		err |= mbuf_write_u32(mb, htonl(rr->rdata.soa.refresh));
+		err |= mbuf_write_u32(mb, htonl(rr->rdata.soa.retry));
+		err |= mbuf_write_u32(mb, htonl(rr->rdata.soa.expire));
+		err |= mbuf_write_u32(mb, htonl(rr->rdata.soa.ttlmin));
+		break;
+
+	case DNS_TYPE_PTR:
+		err |= dns_dname_encode(mb, rr->rdata.ptr.ptrdname,
+					ht_dname, start, true);
+		break;
+
+	case DNS_TYPE_MX:
+		err |= mbuf_write_u16(mb, htons(rr->rdata.mx.pref));
+		err |= dns_dname_encode(mb, rr->rdata.mx.exchange,
+					ht_dname, start, true);
+		break;
+
+	case DNS_TYPE_AAAA:
+		err |= mbuf_write_mem(mb, rr->rdata.aaaa.addr, 16);
+		break;
+
+	case DNS_TYPE_SRV:
+		err |= mbuf_write_u16(mb, htons(rr->rdata.srv.pri));
+		err |= mbuf_write_u16(mb, htons(rr->rdata.srv.weight));
+		err |= mbuf_write_u16(mb, htons(rr->rdata.srv.port));
+		err |= dns_dname_encode(mb, rr->rdata.srv.target,
+					ht_dname, start, false);
+		break;
+
+	case DNS_TYPE_NAPTR:
+		err |= mbuf_write_u16(mb, htons(rr->rdata.naptr.order));
+		err |= mbuf_write_u16(mb, htons(rr->rdata.naptr.pref));
+		err |= dns_cstr_encode(mb, rr->rdata.naptr.flags);
+		err |= dns_cstr_encode(mb, rr->rdata.naptr.services);
+		err |= dns_cstr_encode(mb, rr->rdata.naptr.regexp);
+		err |= dns_dname_encode(mb, rr->rdata.naptr.replace,
+					ht_dname, start, false);
+		break;
+
+	default:
+		err = EINVAL;
+		break;
+	}
+
+	len = mb->pos - start_rdata;
+	mb->pos = start_rdata - 2;
+	err |= mbuf_write_u16(mb, htons(len));
+	mb->pos += len;
+
+	return err;
+}
+
+
+/**
+ * Decode a DNS Resource Record (RR) from a memory buffer
+ *
+ * @param mb    Memory buffer to decode from
+ * @param rr    Pointer to allocated Resource Record
+ * @param start Start position
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_rr_decode(struct mbuf *mb, struct dnsrr **rr, size_t start)
+{
+	int err = 0;
+	struct dnsrr *lrr;
+
+	if (!mb || !rr)
+		return EINVAL;
+
+	lrr = dns_rr_alloc();
+	if (!lrr)
+		return ENOMEM;
+
+	err = dns_dname_decode(mb, &lrr->name, start);
+	if (err)
+		goto error;
+
+	if (mbuf_get_left(mb) < 10)
+		goto fmerr;
+
+	lrr->type     = ntohs(mbuf_read_u16(mb));
+	lrr->dnsclass = ntohs(mbuf_read_u16(mb));
+	lrr->ttl      = ntohl(mbuf_read_u32(mb));
+	lrr->rdlen    = ntohs(mbuf_read_u16(mb));
+
+	if (mbuf_get_left(mb) < lrr->rdlen)
+		goto fmerr;
+
+	switch (lrr->type) {
+
+	case DNS_TYPE_A:
+		if (lrr->rdlen != 4)
+			goto fmerr;
+
+		lrr->rdata.a.addr = ntohl(mbuf_read_u32(mb));
+		break;
+
+	case DNS_TYPE_NS:
+		err = dns_dname_decode(mb, &lrr->rdata.ns.nsdname, start);
+		if (err)
+			goto error;
+
+		break;
+
+	case DNS_TYPE_CNAME:
+		err = dns_dname_decode(mb, &lrr->rdata.cname.cname, start);
+		if (err)
+			goto error;
+
+		break;
+
+	case DNS_TYPE_SOA:
+		err = dns_dname_decode(mb, &lrr->rdata.soa.mname, start);
+		if (err)
+			goto error;
+
+		err = dns_dname_decode(mb, &lrr->rdata.soa.rname, start);
+		if (err)
+			goto error;
+
+		if (mbuf_get_left(mb) < 20)
+			goto fmerr;
+
+		lrr->rdata.soa.serial  = ntohl(mbuf_read_u32(mb));
+		lrr->rdata.soa.refresh = ntohl(mbuf_read_u32(mb));
+		lrr->rdata.soa.retry   = ntohl(mbuf_read_u32(mb));
+		lrr->rdata.soa.expire  = ntohl(mbuf_read_u32(mb));
+		lrr->rdata.soa.ttlmin  = ntohl(mbuf_read_u32(mb));
+		break;
+
+	case DNS_TYPE_PTR:
+		err = dns_dname_decode(mb, &lrr->rdata.ptr.ptrdname, start);
+		if (err)
+			goto error;
+
+		break;
+
+	case DNS_TYPE_MX:
+		if (mbuf_get_left(mb) < 2)
+			goto fmerr;
+
+		lrr->rdata.mx.pref = ntohs(mbuf_read_u16(mb));
+
+		err = dns_dname_decode(mb, &lrr->rdata.mx.exchange, start);
+		if (err)
+			goto error;
+
+		break;
+
+	case DNS_TYPE_AAAA:
+		if (lrr->rdlen != 16)
+			goto fmerr;
+
+		err = mbuf_read_mem(mb, lrr->rdata.aaaa.addr, 16);
+		if (err)
+			goto error;
+		break;
+
+	case DNS_TYPE_SRV:
+		if (mbuf_get_left(mb) < 6)
+			goto fmerr;
+
+		lrr->rdata.srv.pri    = ntohs(mbuf_read_u16(mb));
+		lrr->rdata.srv.weight = ntohs(mbuf_read_u16(mb));
+		lrr->rdata.srv.port   = ntohs(mbuf_read_u16(mb));
+
+		err = dns_dname_decode(mb, &lrr->rdata.srv.target, start);
+		if (err)
+			goto error;
+
+		break;
+
+	case DNS_TYPE_NAPTR:
+		if (mbuf_get_left(mb) < 4)
+			goto fmerr;
+
+		lrr->rdata.naptr.order = ntohs(mbuf_read_u16(mb));
+		lrr->rdata.naptr.pref  = ntohs(mbuf_read_u16(mb));
+
+		err = dns_cstr_decode(mb, &lrr->rdata.naptr.flags);
+		if (err)
+			goto error;
+
+		err = dns_cstr_decode(mb, &lrr->rdata.naptr.services);
+		if (err)
+			goto error;
+
+		err = dns_cstr_decode(mb, &lrr->rdata.naptr.regexp);
+		if (err)
+			goto error;
+
+		err = dns_dname_decode(mb, &lrr->rdata.naptr.replace, start);
+		if (err)
+			goto error;
+
+		break;
+
+	default:
+		mb->pos += lrr->rdlen;
+		break;
+	}
+
+	*rr = lrr;
+
+	return 0;
+
+ fmerr:
+	err = EINVAL;
+ error:
+	mem_deref(lrr);
+
+	return err;
+}
+
+
+/**
+ * Compare two DNS Resource Records
+ *
+ * @param rr1   First Resource Record
+ * @param rr2   Second Resource Record
+ * @param rdata If true, also compares Resource Record data
+ *
+ * @return True if match, false if not match
+ */
+bool dns_rr_cmp(const struct dnsrr *rr1, const struct dnsrr *rr2, bool rdata)
+{
+	if (!rr1 || !rr2)
+		return false;
+
+	if (rr1 == rr2)
+		return true;
+
+	if (rr1->type != rr2->type)
+		return false;
+
+	if (rr1->dnsclass != rr2->dnsclass)
+		return false;
+
+	if (str_casecmp(rr1->name, rr2->name))
+		return false;
+
+	if (!rdata)
+		return true;
+
+	switch (rr1->type) {
+
+	case DNS_TYPE_A:
+		if (rr1->rdata.a.addr != rr2->rdata.a.addr)
+			return false;
+
+		break;
+
+	case DNS_TYPE_NS:
+		if (str_casecmp(rr1->rdata.ns.nsdname, rr2->rdata.ns.nsdname))
+			return false;
+
+		break;
+
+	case DNS_TYPE_CNAME:
+		if (str_casecmp(rr1->rdata.cname.cname,
+				rr2->rdata.cname.cname))
+			return false;
+
+		break;
+
+	case DNS_TYPE_SOA:
+		if (str_casecmp(rr1->rdata.soa.mname, rr2->rdata.soa.mname))
+			return false;
+
+		if (str_casecmp(rr1->rdata.soa.rname, rr2->rdata.soa.rname))
+			return false;
+
+		if (rr1->rdata.soa.serial != rr2->rdata.soa.serial)
+			return false;
+
+		if (rr1->rdata.soa.refresh != rr2->rdata.soa.refresh)
+			return false;
+
+		if (rr1->rdata.soa.retry != rr2->rdata.soa.retry)
+			return false;
+
+		if (rr1->rdata.soa.expire != rr2->rdata.soa.expire)
+			return false;
+
+		if (rr1->rdata.soa.ttlmin != rr2->rdata.soa.ttlmin)
+			return false;
+
+		break;
+
+	case DNS_TYPE_PTR:
+		if (str_casecmp(rr1->rdata.ptr.ptrdname,
+				rr2->rdata.ptr.ptrdname))
+			return false;
+
+		break;
+
+	case DNS_TYPE_MX:
+		if (rr1->rdata.mx.pref != rr2->rdata.mx.pref)
+			return false;
+
+		if (str_casecmp(rr1->rdata.mx.exchange,
+				rr2->rdata.mx.exchange))
+			return false;
+
+		break;
+
+	case DNS_TYPE_AAAA:
+		if (memcmp(rr1->rdata.aaaa.addr, rr2->rdata.aaaa.addr, 16))
+			return false;
+
+		break;
+
+	case DNS_TYPE_SRV:
+		if (rr1->rdata.srv.pri != rr2->rdata.srv.pri)
+			return false;
+
+		if (rr1->rdata.srv.weight != rr2->rdata.srv.weight)
+			return false;
+
+		if (rr1->rdata.srv.port != rr2->rdata.srv.port)
+			return false;
+
+		if (str_casecmp(rr1->rdata.srv.target, rr2->rdata.srv.target))
+			return false;
+
+		break;
+
+	case DNS_TYPE_NAPTR:
+		if (rr1->rdata.naptr.order != rr2->rdata.naptr.order)
+			return false;
+
+		if (rr1->rdata.naptr.pref != rr2->rdata.naptr.pref)
+			return false;
+
+		/* todo check case sensitiveness */
+		if (str_casecmp(rr1->rdata.naptr.flags,
+				rr2->rdata.naptr.flags))
+			return false;
+
+		/* todo check case sensitiveness */
+		if (str_casecmp(rr1->rdata.naptr.services,
+				rr2->rdata.naptr.services))
+			return false;
+
+		/* todo check case sensitiveness */
+		if (str_casecmp(rr1->rdata.naptr.regexp,
+				rr2->rdata.naptr.regexp))
+			return false;
+
+		/* todo check case sensitiveness */
+		if (str_casecmp(rr1->rdata.naptr.replace,
+				rr2->rdata.naptr.replace))
+			return false;
+
+		break;
+
+	default:
+		return false;
+	}
+
+	return true;
+}
+
+
+/**
+ * Get the DNS Resource Record (RR) name
+ *
+ * @param type DNS Resource Record type
+ *
+ * @return DNS Resource Record name
+ */
+const char *dns_rr_typename(uint16_t type)
+{
+	switch (type) {
+
+	case DNS_TYPE_A:     return "A";
+	case DNS_TYPE_NS:    return "NS";
+	case DNS_TYPE_CNAME: return "CNAME";
+	case DNS_TYPE_SOA:   return "SOA";
+	case DNS_TYPE_PTR:   return "PTR";
+	case DNS_TYPE_MX:    return "MX";
+	case DNS_TYPE_AAAA:  return "AAAA";
+	case DNS_TYPE_SRV:   return "SRV";
+	case DNS_TYPE_NAPTR: return "NAPTR";
+	case DNS_QTYPE_IXFR: return "IXFR";
+	case DNS_QTYPE_AXFR: return "AXFR";
+	case DNS_QTYPE_ANY:  return "ANY";
+	default:             return "??";
+	}
+}
+
+
+/**
+ * Get the DNS Resource Record (RR) class name
+ *
+ * @param dnsclass DNS Class
+ *
+ * @return DNS Class name
+ */
+const char *dns_rr_classname(uint16_t dnsclass)
+{
+	switch (dnsclass) {
+
+	case DNS_CLASS_IN:   return "IN";
+	case DNS_QCLASS_ANY: return "ANY";
+	default:             return "??";
+	}
+}
+
+
+/**
+ * Print a DNS Resource Record
+ *
+ * @param pf Print function
+ * @param rr DNS Resource Record
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_rr_print(struct re_printf *pf, const struct dnsrr *rr)
+{
+	static const size_t w = 24;
+	struct sa sa;
+	size_t n, l;
+	int err;
+
+	if (!pf || !rr)
+		return EINVAL;
+
+	l = str_len(rr->name);
+	n = (w > l) ? w - l : 0;
+
+	err = re_hprintf(pf, "%s.", rr->name);
+	while (n--)
+		err |= pf->vph(" ", 1, pf->arg);
+
+	err |= re_hprintf(pf, " %10lld %-4s %-7s ",
+			  rr->ttl,
+			  dns_rr_classname(rr->dnsclass),
+			  dns_rr_typename(rr->type));
+
+	switch (rr->type) {
+
+	case DNS_TYPE_A:
+		sa_set_in(&sa, rr->rdata.a.addr, 0);
+		err |= re_hprintf(pf, "%j", &sa);
+		break;
+
+	case DNS_TYPE_NS:
+		err |= re_hprintf(pf, "%s.", rr->rdata.ns.nsdname);
+		break;
+
+	case DNS_TYPE_CNAME:
+		err |= re_hprintf(pf, "%s.", rr->rdata.cname.cname);
+		break;
+
+	case DNS_TYPE_SOA:
+		err |= re_hprintf(pf, "%s. %s. %u %u %u %u %u",
+				  rr->rdata.soa.mname,
+				  rr->rdata.soa.rname,
+				  rr->rdata.soa.serial,
+				  rr->rdata.soa.refresh,
+				  rr->rdata.soa.retry,
+				  rr->rdata.soa.expire,
+				  rr->rdata.soa.ttlmin);
+		break;
+
+	case DNS_TYPE_PTR:
+		err |= re_hprintf(pf, "%s.", rr->rdata.ptr.ptrdname);
+		break;
+
+	case DNS_TYPE_MX:
+		err |= re_hprintf(pf, "%3u %s.", rr->rdata.mx.pref,
+				  rr->rdata.mx.exchange);
+		break;
+
+	case DNS_TYPE_AAAA:
+		sa_set_in6(&sa, rr->rdata.aaaa.addr, 0);
+		err |= re_hprintf(pf, "%j", &sa);
+		break;
+
+	case DNS_TYPE_SRV:
+		err |= re_hprintf(pf, "%3u %3u %u %s.",
+				  rr->rdata.srv.pri,
+				  rr->rdata.srv.weight,
+				  rr->rdata.srv.port,
+				  rr->rdata.srv.target);
+		break;
+
+	case DNS_TYPE_NAPTR:
+		err |= re_hprintf(pf, "%3u %3u \"%s\" \"%s\" \"%s\" %s.",
+				  rr->rdata.naptr.order,
+				  rr->rdata.naptr.pref,
+				  rr->rdata.naptr.flags,
+				  rr->rdata.naptr.services,
+				  rr->rdata.naptr.regexp,
+				  rr->rdata.naptr.replace);
+		break;
+
+	default:
+		err |= re_hprintf(pf, "?");
+		break;
+	}
+
+	return err;
+}
diff --git a/src/dns/rrlist.c b/src/dns/rrlist.c
new file mode 100644
index 0000000..6e7c572
--- /dev/null
+++ b/src/dns/rrlist.c
@@ -0,0 +1,243 @@
+/**
+ * @file rrlist.c  DNS Resource Records list
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_mbuf.h>
+#include <re_fmt.h>
+#include <re_dns.h>
+
+
+enum {
+	CNAME_RECURSE_MAX = 16,
+};
+
+
+struct sort {
+	uint16_t type;
+	uint32_t key;
+};
+
+
+static uint32_t sidx(const struct dnsrr *rr, uint32_t key)
+{
+	uint32_t addr[4];
+
+	switch (rr->type) {
+
+	case DNS_TYPE_A:
+		return rr->rdata.a.addr ^ key;
+
+	case DNS_TYPE_AAAA:
+		memcpy(addr, rr->rdata.aaaa.addr, 16);
+
+		return addr[0] ^ addr[1] ^ addr[2] ^ addr[3] ^ key;
+
+	case DNS_TYPE_SRV:
+		return ((hash_fast_str(rr->rdata.srv.target) & 0xfff) ^ key) +
+			rr->rdata.srv.weight;
+
+	default:
+		return 0;
+	}
+}
+
+
+static bool std_sort_handler(struct le *le1, struct le *le2, void *arg)
+{
+	struct dnsrr *rr1 = le1->data;
+	struct dnsrr *rr2 = le2->data;
+	struct sort *sort = arg;
+
+	if (sort->type != rr1->type)
+		return sort->type != rr2->type;
+
+	if (sort->type != rr2->type)
+		return true;
+
+	switch (sort->type) {
+
+	case DNS_TYPE_MX:
+		return rr1->rdata.mx.pref <= rr2->rdata.mx.pref;
+
+	case DNS_TYPE_SRV:
+		if (rr1->rdata.srv.pri == rr2->rdata.srv.pri)
+			return sidx(rr1, sort->key) >= sidx(rr2, sort->key);
+
+		return rr1->rdata.srv.pri < rr2->rdata.srv.pri;
+
+	case DNS_TYPE_NAPTR:
+		if (rr1->rdata.naptr.order == rr2->rdata.naptr.order)
+			return rr1->rdata.naptr.pref <= rr2->rdata.naptr.pref;
+
+		return rr1->rdata.naptr.order < rr2->rdata.naptr.order;
+
+	default:
+		break;
+	}
+
+	return true;
+}
+
+
+static bool addr_sort_handler(struct le *le1, struct le *le2, void *arg)
+{
+	struct dnsrr *rr1 = le1->data;
+	struct dnsrr *rr2 = le2->data;
+	struct sort *sort = arg;
+
+	return sidx(rr1, sort->key) >= sidx(rr2, sort->key);
+}
+
+
+/**
+ * Sort a list of DNS Resource Records
+ *
+ * @param rrl  DNS Resource Record list
+ * @param type DNS Record type
+ * @param key  Sort key
+ */
+void dns_rrlist_sort(struct list *rrl, uint16_t type, size_t key)
+{
+	struct sort sort = {type, (uint32_t)key>>5};
+
+	list_sort(rrl, std_sort_handler, &sort);
+}
+
+
+/**
+ * Sort a list of A/AAAA DNS Resource Records
+ *
+ * @param rrl  DNS Resource Record list
+ * @param key  Sort key
+ */
+void dns_rrlist_sort_addr(struct list *rrl, size_t key)
+{
+	struct sort sort = {0, (uint32_t)key>>5};
+
+	list_sort(rrl, addr_sort_handler, &sort);
+}
+
+
+static struct dnsrr *rrlist_apply(struct list *rrl, const char *name,
+				  uint16_t type1, uint16_t type2,
+				  uint16_t dnsclass,
+				  bool recurse, uint32_t depth,
+				  dns_rrlist_h *rrlh, void *arg)
+{
+	struct le *le = list_head(rrl);
+
+	if (depth > CNAME_RECURSE_MAX)
+		return NULL;
+
+	while (le) {
+
+		struct dnsrr *rr = le->data;
+
+		le = le->next;
+
+		if (name && str_casecmp(name, rr->name))
+			continue;
+
+		if (type1 != DNS_QTYPE_ANY && type2 != DNS_QTYPE_ANY &&
+		    rr->type != type1 && rr->type != type2 &&
+		    (rr->type != DNS_TYPE_CNAME || !recurse))
+			continue;
+
+		if (dnsclass != DNS_QCLASS_ANY && rr->dnsclass != dnsclass)
+			continue;
+
+		if (!rrlh || rrlh(rr, arg))
+			return rr;
+
+		if (recurse &&
+		    DNS_QTYPE_ANY != type1 && DNS_QTYPE_ANY != type2 &&
+		    DNS_TYPE_CNAME != type1 && DNS_TYPE_CNAME != type2 &&
+		    DNS_TYPE_CNAME == rr->type) {
+			rr = rrlist_apply(rrl, rr->rdata.cname.cname, type1,
+					  type2, dnsclass, recurse, ++depth,
+					  rrlh, arg);
+			if (rr)
+				return rr;
+		}
+	}
+
+	return NULL;
+}
+
+
+/**
+ * Apply a function handler to a list of DNS Resource Records
+ *
+ * @param rrl      DNS Resource Record list
+ * @param name     If set, filter on domain name
+ * @param type     If not DNS_QTYPE_ANY, filter on record type
+ * @param dnsclass If not DNS_QCLASS_ANY, filter on DNS class
+ * @param recurse  Cname recursion
+ * @param rrlh     Resource record handler
+ * @param arg      Handler argument
+ *
+ * @return Matching Resource Record or NULL
+ */
+struct dnsrr *dns_rrlist_apply(struct list *rrl, const char *name,
+			       uint16_t type, uint16_t dnsclass,
+			       bool recurse, dns_rrlist_h *rrlh, void *arg)
+{
+	return rrlist_apply(rrl, name, type, type, dnsclass,
+			    recurse, 0, rrlh, arg);
+}
+
+
+/**
+ * Apply a function handler to a list of DNS Resource Records (two types)
+ *
+ * @param rrl      DNS Resource Record list
+ * @param name     If set, filter on domain name
+ * @param type1    If not DNS_QTYPE_ANY, filter on record type
+ * @param type2    If not DNS_QTYPE_ANY, filter on record type
+ * @param dnsclass If not DNS_QCLASS_ANY, filter on DNS class
+ * @param recurse  Cname recursion
+ * @param rrlh     Resource record handler
+ * @param arg      Handler argument
+ *
+ * @return Matching Resource Record or NULL
+ */
+struct dnsrr *dns_rrlist_apply2(struct list *rrl, const char *name,
+				uint16_t type1, uint16_t type2,
+				uint16_t dnsclass, bool recurse,
+				dns_rrlist_h *rrlh, void *arg)
+{
+	return rrlist_apply(rrl, name, type1, type2, dnsclass,
+			    recurse, 0, rrlh, arg);
+}
+
+
+static bool find_handler(struct dnsrr *rr, void *arg)
+{
+	uint16_t type = *(uint16_t *)arg;
+
+	return rr->type == type;
+}
+
+
+/**
+ * Find a DNS Resource Record in a list
+ *
+ * @param rrl      Resource Record list
+ * @param name     If set, filter on domain name
+ * @param type     If not DNS_QTYPE_ANY, filter on record type
+ * @param dnsclass If not DNS_QCLASS_ANY, filter on DNS class
+ * @param recurse  Cname recursion
+ *
+ * @return Matching Resource Record or NULL
+ */
+struct dnsrr *dns_rrlist_find(struct list *rrl, const char *name,
+			      uint16_t type, uint16_t dnsclass, bool recurse)
+{
+	return rrlist_apply(rrl, name, type, type, dnsclass,
+			    recurse, 0, find_handler, &type);
+}
diff --git a/src/dns/win32/srv.c b/src/dns/win32/srv.c
new file mode 100644
index 0000000..0ca0749
--- /dev/null
+++ b/src/dns/win32/srv.c
@@ -0,0 +1,100 @@
+/**
+ * @file win32/srv.c  Get DNS Server IP code for Windows
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <winsock2.h>
+#include <iphlpapi.h>
+#include <io.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_dns.h>
+#include "../dns.h"
+
+
+#define DEBUG_MODULE "win32/srv"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+int get_windns(char *domain, size_t dsize, struct sa *srvv, uint32_t *n)
+{
+	FIXED_INFO *     FixedInfo = NULL;
+	ULONG            ulOutBufLen;
+	DWORD            dwRetVal;
+	IP_ADDR_STRING * pIPAddr;
+	HANDLE           hLib;
+	union {
+		FARPROC proc;
+		DWORD (WINAPI *_GetNetworkParams)(FIXED_INFO*, DWORD*);
+	} u;
+	uint32_t i;
+	int err;
+
+	if (!srvv || !n || !*n)
+		return EINVAL;
+
+	hLib = LoadLibrary(TEXT("iphlpapi.dll"));
+	if (!hLib)
+		return ENOSYS;
+
+	u.proc = GetProcAddress(hLib, TEXT("GetNetworkParams"));
+	if (!u.proc) {
+		err = ENOSYS;
+		goto out;
+	}
+
+	FixedInfo = (FIXED_INFO *)GlobalAlloc(GPTR, sizeof( FIXED_INFO ));
+	ulOutBufLen = sizeof( FIXED_INFO );
+
+	if (ERROR_BUFFER_OVERFLOW == (*u._GetNetworkParams)(FixedInfo,
+							    &ulOutBufLen)) {
+		GlobalFree( FixedInfo );
+		FixedInfo = (FIXED_INFO *)GlobalAlloc(GPTR, ulOutBufLen);
+	}
+
+	if ((dwRetVal = (*u._GetNetworkParams)( FixedInfo, &ulOutBufLen ))) {
+		DEBUG_WARNING("couldn't get network params (%d)\n", dwRetVal);
+		err = ENOENT;
+		goto out;
+	}
+
+	str_ncpy(domain, FixedInfo->DomainName, dsize);
+
+#if 0
+	printf( "Host Name: %s\n", FixedInfo->HostName);
+	printf( "Domain Name: %s\n", FixedInfo->DomainName);
+	printf( "DNS Servers:\n" );
+	printf( "\t%s\n", FixedInfo->DnsServerList.IpAddress.String );
+#endif
+
+	i = 0;
+	pIPAddr = &FixedInfo->DnsServerList;
+	while (pIPAddr && strlen(pIPAddr->IpAddress.String) > 0) {
+		err = sa_set_str(&srvv[i], pIPAddr->IpAddress.String,
+				 DNS_PORT);
+		if (err) {
+			DEBUG_WARNING("sa_set_str: %s (%m)\n",
+				      pIPAddr->IpAddress.String, err);
+		}
+		DEBUG_INFO("dns ip %u: %j\n", i, &srvv[i]);
+		++i;
+		pIPAddr = pIPAddr ->Next;
+
+		if (i >= *n)
+			break;
+	}
+
+	*n = i;
+	DEBUG_INFO("got %u nameservers\n", i);
+	err = i>0 ? 0 : ENOENT;
+
+ out:
+	if (FixedInfo)
+		GlobalFree(FixedInfo);
+	FreeLibrary(hLib);
+	return err;
+}