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;
+}