New DNS backend: c-ares
authorMarko Kreen <markokr@gmail.com>
Mon, 28 May 2012 22:08:11 +0000 (01:08 +0300)
committerMarko Kreen <markokr@gmail.com>
Mon, 3 Dec 2012 14:11:18 +0000 (16:11 +0200)
Supports all interesting features, unlike other backends:
- /etc/hosts with refresh
- SOA lookup
- Large replies (via TCP/EDNS+UDP)
- IPv6

Makefile
README
config.mak.in
configure.ac
include/dnslookup.h
src/dnslookup.c
src/main.c

index 0e8598b759cf254c320c442e1e3707293f33cf98..a503148b2d16be9340d09e585e2d3f8fbcc8c34d 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -40,7 +40,7 @@ pgbouncer_SOURCES = \
        include/util.h \
        include/varcache.h
 
-pgbouncer_CPPFLAGS = -Iinclude
+pgbouncer_CPPFLAGS = -Iinclude $(CARES_CFLAGS)
 
 # include libusual sources directly
 AM_FEATURES = libusual
@@ -83,7 +83,7 @@ endif
 # win32
 #
 
-pgbouncer_LDADD := $(LIBS)
+pgbouncer_LDADD := $(CARES_LIBS) $(LIBS)
 LIBS :=
 
 EXTRA_pgbouncer_SOURCES = win32/win32support.c win32/win32support.h
diff --git a/README b/README
index 18141f2fc131302f6a1e0499822d6fdeb098493d..3a005cce7d6ae7acc611ab7d79680f41cdaf5a80 100644 (file)
--- a/README
+++ b/README
@@ -41,8 +41,9 @@ and their probing order:
 |===========================================================================================
 | backend                    | parallel | EDNS0 (1) | /etc/hosts | SOA lookup (2) | note
 
+| c-ares                     | yes      | yes       | yes        | yes            |
 | udns                       | yes      | yes       | no         | yes            | ipv4-only
-| evdns, libevent 2.x        | yes      | no        | yes        | no             |
+| evdns, libevent 2.x        | yes      | no        | yes        | no             | does not check /etc/hosts updates
 | getaddrinfo_a, glibc 2.9+  | yes      | yes (3)   | yes        | no             | N/A on non-linux
 | getaddrinfo, libc          | no       | yes (3)   | yes        | no             | N/A on win32, requires pthreads
 | evdns, libevent 1.x        | yes      | no        | no         | no             | buggy
index 1cd2e8077ed08d8f6a45767cd7cb08c1c9865c68..5345c050c105ffbbec12f6dbd06930eceef94384 100644 (file)
@@ -52,8 +52,8 @@ abs_top_builddir ?= @abs_top_builddir@
 nosub_top_srcdir ?= @top_srcdir@
 nosub_top_builddir ?= @top_builddir@
 
-
-
+CARES_CFLAGS = @CARES_CFLAGS@
+CARES_LIBS = @CARES_LIBS@
 
 XMLTO = @XMLTO@
 ASCIIDOC = @ASCIIDOC@
index cd5b51521e8067b3266d8d89025995c1283f6a97..2fff46a19a6b3f5fe6e08a852ff47accc848ba41 100644 (file)
@@ -11,6 +11,8 @@ AC_USUAL_INIT
 dnl Checks for programs.
 AC_USUAL_PROGRAM_CHECK
 
+PKG_PROG_PKG_CONFIG
+
 dnl asciidoc >= 8.4
 AC_CHECK_PROGS(ASCIIDOC, asciidoc)
 if test -n "$ASCIIDOC"; then
@@ -75,8 +77,49 @@ AC_CHECK_FUNCS(crypt lstat)
 dnl Find libevent
 AC_USUAL_LIBEVENT
 
-dnl Find libudns
+##
+## DNS backend
+##
+
+# make sure all vars are set
+use_cares=auto
 use_udns=no
+use_evdns=no
+
+dnl Find c-ares
+AC_MSG_CHECKING([whether to use c-ares for DNS lookups])
+AC_ARG_WITH(cares,
+  AC_HELP_STRING([--with-cares=prefix],[Specify where c-ares is installed]),
+  [ if test "$withval" = "no"; then
+      use_cares=no
+    elif test "$withval" = "yes"; then
+      use_cares=yes
+    else
+      use_cares=yes
+      CARES_CFLAGS="-I$withval/include"
+      CARES_LIBS="-L$withval/lib -lcares"
+    fi
+  ], [])
+AC_MSG_RESULT([$use_cares])
+
+if test "$use_cares" = "auto"; then
+  PKG_CHECK_MODULES(CARES, [libcares >= 1.6.0], [use_cares=yes], [use_cares=no])
+fi
+
+if test "$use_cares" = "yes"; then
+  AC_DEFINE(USE_CARES, 1, [Use c-ares for name resolution.])
+
+  # does it support SOA parse
+  tmp_CFLAGS="$CFLAGS"
+  tmp_LIBS="$LIBS"
+  CFLAGS="$CARES_CFLAGS $CFLAGS"
+  LIBS="$CARES_LIBS $LIBS"
+  AC_CHECK_FUNCS(ares_parse_soa_reply)
+  LIBS="$tmp_LIBS"
+  CFLAGS="$tmp_CFLAGS"
+else
+
+dnl Find libudns
 AC_MSG_CHECKING([whether to use libudns])
 AC_ARG_WITH(udns,
   AC_HELP_STRING([--with-udns=prefix],[Specify where udns is installed]),
@@ -112,7 +155,6 @@ if test "$use_udns" = "yes"; then
 else # !udns
 
 dnl On libevent 2.x use evdns by default
-use_evdns=no
 if test "$ac_cv_func_evdns_base_new" = "yes"; then
   use_evdns=yes
 fi
@@ -129,12 +171,16 @@ else
 fi
 
 dnl Check if need getaddinfo_a compat
-if test "$use_evdns" = no; then
+if test "$use_udns.$use_cares.$use_evdns" = "no.no.no"; then
   AC_USUAL_GETADDRINFO_A
 fi
 
 fi # !udns
 
+fi # !cares
+
+## end of DNS
+
 AC_USUAL_DEBUG
 AC_USUAL_CASSERT
 AC_USUAL_WERROR
index 797c1d24cf571eba849b2150d957f8dfb8dda7eb..3c4e90fdebd689f71e7ef0c6025c7b26f6be2c64 100644 (file)
@@ -41,3 +41,6 @@ typedef void (*adns_walk_zone_f)(void *arg, const char *name, uint32_t serial, i
 
 void adns_walk_names(struct DNSContext *ctx, adns_walk_name_f cb, void *arg);
 void adns_walk_zones(struct DNSContext *ctx, adns_walk_zone_f cb, void *arg);
+
+void adns_per_loop(struct DNSContext *ctx);
+
index f015d4beebdb1556cfadf6b8e243e9e5ddb91f13..f236a044c3978db052459cafb96129b87c7d6a8e 100644 (file)
 /*
  * Available backends:
  *
+ * c-ares - libcares
  * udns - libudns
  * getaddrinfo_a - glibc only
  * libevent1 - returns TTL, ignores hosts file.
  * libevent2 - does not return TTL, uses hosts file.
  */
 
-#if !defined(USE_EVDNS) && !defined(USE_UDNS)
+#if !defined(USE_EVDNS) && !defined(USE_UDNS) && !defined(USE_CARES)
 #define USE_GETADDRINFO_A
 #endif
 
 #endif /* !EV_ET */
 #endif /* USE_EVDNS */
 
+#ifdef USE_CARES
+#include <ares.h>
+#include <ares_dns.h>
+#include <arpa/nameser.h>
+#define ZONE_RECHECK 1
+#else
+/* only c-ares requires this */
+#define impl_per_loop(ctx)
+#endif
+
 #ifdef USE_UDNS
 #include <udns.h>
 #define ZONE_RECHECK 1
@@ -132,28 +143,42 @@ static void got_zone_serial(struct DNSContext *ctx, uint32_t *serial);
  * Custom addrinfo generation
  */
 
-#if defined(USE_LIBEVENT1) || defined(USE_UDNS)
+#if defined(USE_LIBEVENT1) || defined(USE_UDNS) || defined(USE_CARES)
 
-static struct addrinfo *mk_addrinfo(const struct in_addr ip4)
+static struct addrinfo *mk_addrinfo(const void *adr, int af)
 {
        struct addrinfo *ai;
-       struct sockaddr_in *sa;
+
        ai = calloc(1, sizeof(*ai));
        if (!ai)
                return NULL;
-       sa = calloc(1, sizeof(*sa));
-       if (!sa) {
-               free(ai);
-               return NULL;
+
+       if (af == AF_INET) {
+               struct sockaddr_in *sa4;
+               sa4 = calloc(1, sizeof(*sa4));
+               if (!sa4)
+                       goto failed;
+               memcpy(&sa4->sin_addr, adr, 4);
+               sa4->sin_family = af;
+               ai->ai_addr = (struct sockaddr *)sa4;
+               ai->ai_addrlen = sizeof(*sa4);
+       } else if (af == AF_INET6) {
+               struct sockaddr_in6 *sa6;
+               sa6 = calloc(1, sizeof(*sa6));
+               if (!sa6)
+                       goto failed;
+               memcpy(&sa6->sin6_addr, adr, sizeof(*sa6));
+               sa6->sin6_family = af;
+               ai->ai_addr = (struct sockaddr *)sa6;
+               ai->ai_addrlen = sizeof(*sa6);
        }
-       sa->sin_addr = ip4;
-       sa->sin_family = AF_INET;
-       ai->ai_addr = (struct sockaddr *)sa;
-       ai->ai_addrlen = sizeof(*sa);
        ai->ai_protocol = IPPROTO_TCP;
        ai->ai_socktype = SOCK_STREAM;
-       ai->ai_family = AF_INET;
+       ai->ai_family = af;
        return ai;
+failed:
+       free(ai);
+       return NULL;
 }
 
 #define freeaddrinfo(x) local_freeaddrinfo(x)
@@ -169,21 +194,47 @@ static void freeaddrinfo(struct addrinfo *ai)
        }
 }
 
-static struct addrinfo *convert_ipv4_result(const struct in_addr *adrs, int count)
+static inline struct addrinfo *convert_ipv4_result(const struct in_addr *adrs, int count)
 {
-       struct addrinfo *ai, *last = NULL;
+       struct addrinfo *ai, *first = NULL, *last = NULL;
        int i;
 
-       for (i = count - 1; i >= 0; i--) {
-               ai = mk_addrinfo(adrs[i]);
+       for (i = 0; i < count; i++) {
+               ai = mk_addrinfo(&adrs[i], AF_INET);
                if (!ai)
                        goto failed;
-               ai->ai_next = last;
+
+               if (!first)
+                       first = ai;
+               else
+                       last->ai_next = ai;
                last = ai;
        }
-       return last;
+       return first;
 failed:
-       freeaddrinfo(last);
+       freeaddrinfo(first);
+       return NULL;
+}
+
+static inline struct addrinfo *convert_hostent(const struct hostent *h)
+{
+       struct addrinfo *ai, *first = NULL, *last = NULL;
+       int i;
+
+       for (i = 0; h->h_addr_list[i]; i++) {
+               ai = mk_addrinfo(h->h_addr_list[i], h->h_addrtype);
+               if (!ai)
+                       goto failed;
+
+               if (!first)
+                       first = ai;
+               else
+                       last->ai_next = ai;
+               last = ai;
+       }
+       return first;
+failed:
+       freeaddrinfo(first);
        return NULL;
 }
 
@@ -676,6 +727,445 @@ static int impl_query_soa_serial(struct DNSContext *ctx, const char *zonename)
 #endif /* USE_UDNS */
 
 
+/*
+ * ADNS with <ares.h>
+ */
+
+#ifdef USE_CARES
+
+#define MAX_CARES_FDS 16
+
+struct XaresFD {
+       struct event ev;                /* fd event state is persistent */
+       struct XaresMeta *meta;         /* pointer to parent context */
+       ares_socket_t sock;             /* socket value */
+       short wait;                     /* EV_READ / EV_WRITE */
+       bool in_use;                    /* is this slot assigned */
+};
+
+struct XaresMeta {
+       /* c-ares descriptor */
+       ares_channel chan;
+
+       /* how many elements in fds array are in use */
+       int max_fds;
+
+       /* static array for fds */
+       struct XaresFD fds[MAX_CARES_FDS];
+
+       /* timer event is one-shot */
+       struct event ev_timer;
+
+       /* is timer activated? */
+       bool timer_active;
+
+       /* If dns events happened during event loop,
+          timer may need recalibration. */
+       int got_events;
+
+};
+
+const char *adns_get_backend(void)
+{
+       return "c-ares " ARES_VERSION_STR;
+}
+
+
+/* called by libevent on timer timeout */
+static void xares_timer_cb(int sock, short flags, void *arg)
+{
+       struct DNSContext *ctx = arg;
+       struct XaresMeta *meta = ctx->edns;
+
+       ares_process_fd(meta->chan, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
+
+       meta->timer_active = 0;
+       meta->got_events = 1;
+}
+
+/* called by libevent on fd event */
+static void xares_fd_cb(int sock, short flags, void *arg)
+{
+       struct XaresFD *xfd = arg;
+       struct XaresMeta *meta = xfd->meta;
+       ares_socket_t r, w;
+
+       r = (flags & EV_READ) ? xfd->sock : ARES_SOCKET_BAD;
+       w = (flags & EV_WRITE) ? xfd->sock : ARES_SOCKET_BAD;
+       ares_process_fd(meta->chan, r, w);
+
+       meta->got_events = 1;
+}
+
+/* called by c-ares on new socket creation */
+static int xares_new_socket_cb(ares_socket_t sock, int sock_type, void *arg)
+{
+       struct DNSContext *ctx = arg;
+       struct XaresMeta *meta = ctx->edns;
+       struct XaresFD *xfd;
+       int pos;
+
+       /* find free slot in array */
+       for (pos = 0; pos < meta->max_fds; pos++) {
+               if (!meta->fds[pos].in_use)
+                       break;
+       }
+       if (pos >= MAX_CARES_FDS) {
+               log_warning("c-ares fd overflow");
+               return ARES_ENOMEM;
+       }
+       if (pos == meta->max_fds)
+               meta->max_fds++;
+
+       /* fill it */
+       xfd = &meta->fds[pos];
+       xfd->meta = meta;
+       xfd->sock = sock;
+       xfd->wait = 0;
+       xfd->in_use = 1;
+       return ARES_SUCCESS;
+}
+
+/* called by c-ares on socket state change (r=w=0 means socket close) */
+static void xares_state_cb(void *arg, ares_socket_t sock, int r, int w)
+{
+       struct DNSContext *ctx = arg;
+       struct XaresMeta *meta = ctx->edns;
+       struct XaresFD *xfd;
+       int pos;
+       short new_wait = 0;
+
+       if (r)
+               new_wait |= EV_READ;
+       if (w)
+               new_wait |= EV_WRITE;
+
+       /* find socket */
+       for (pos = 0; pos < meta->max_fds; pos++) {
+               xfd = &meta->fds[pos];
+               if (!xfd->in_use)
+                       continue;
+
+               if (xfd->sock != sock)
+                       continue;
+
+               /* no change? */
+               if (xfd->wait == new_wait)
+                       return;
+
+               goto re_set;
+       }
+
+       log_warning("adns: c-ares state change for unknown fd: %u", (unsigned)sock);
+       return;
+
+re_set:
+       if (xfd->wait)
+               event_del(&xfd->ev);
+       xfd->wait = new_wait;
+       if (new_wait) {
+               event_set(&xfd->ev, sock, new_wait | EV_PERSIST, xares_fd_cb, xfd);
+               event_add(&xfd->ev, NULL);
+       } else {
+               xfd->in_use = 0;
+       }
+       return;
+}
+
+/* called by c-ares on dns reply */
+static void xares_host_cb(void *arg, int status, int timeouts, struct hostent *h)
+{
+       struct DNSRequest *req = arg;
+       struct addrinfo *res = NULL;
+
+       log_noise("dns: xares_host_cb(%s)=%s", req->name, ares_strerror(status));
+       if (status == ARES_SUCCESS) {
+               res = convert_hostent(h);
+               got_result_gai(0, res, req);
+       } else {
+               log_debug("DNS lookup failed: %s - %s", req->name, ares_strerror(status));
+               got_result_gai(0, res, req);
+       }
+}
+
+/* send hostname query */
+static void impl_launch_query(struct DNSRequest *req)
+{
+       struct XaresMeta *meta = req->ctx->edns;
+
+       log_noise("dns: ares_gethostbyname(%s)", req->name);
+       ares_gethostbyname(meta->chan, req->name, AF_UNSPEC, xares_host_cb, req);
+
+       meta->got_events = 1;
+}
+
+/* re-set timer if any dns event happened */
+static void impl_per_loop(struct DNSContext *ctx)
+{
+       struct timeval tv, *tvp;
+       struct XaresMeta *meta = ctx->edns;
+
+       if (!meta->got_events)
+               return;
+
+       if (meta->timer_active) {
+               event_del(&meta->ev_timer);
+               meta->timer_active = false;
+       }
+
+       tvp = ares_timeout(meta->chan, NULL, &tv);
+       if (tvp != NULL) {
+               event_add(&meta->ev_timer, tvp);
+               meta->timer_active = true;
+       }
+
+       meta->got_events = 0;
+}
+
+/* c-ares setup */
+static bool impl_init(struct DNSContext *ctx)
+{
+       struct XaresMeta *meta;
+       int err;
+       int mask;
+       struct ares_options opts;
+
+       err = ares_library_init(ARES_LIB_INIT_ALL);
+       if (err) {
+               log_error("ares_library_init: %s", ares_strerror(err));
+               return false;
+       }
+
+       meta = calloc(1, sizeof(*meta));
+       if (!meta)
+               return false;
+
+       memset(&opts, 0, sizeof(opts));
+       opts.sock_state_cb = xares_state_cb;
+       opts.sock_state_cb_data = ctx;
+       mask = ARES_OPT_SOCK_STATE_CB;
+
+       err = ares_init_options(&meta->chan, &opts, mask);
+       if (err) {
+               free(meta);
+               log_error("ares_library_init: %s", ares_strerror(err));
+               return false;
+       }
+
+       ares_set_socket_callback(meta->chan, xares_new_socket_cb, ctx);
+
+       evtimer_set(&meta->ev_timer, xares_timer_cb, ctx);
+
+       ctx->edns = meta;
+       return true;
+}
+
+/* c-ares shutdown */
+static void impl_release(struct DNSContext *ctx)
+{
+       struct XaresMeta *meta = ctx->edns;
+
+       ares_destroy(meta->chan);
+       ares_library_cleanup();
+
+       if (meta->timer_active)
+               event_del(&meta->ev_timer);
+
+       free(meta);
+       ctx->edns = NULL;
+}
+
+/*
+ * query SOA with c-ares
+ */
+
+#ifndef HAVE_ARES_PARSE_SOA_REPLY
+
+#define ares_soa_reply         xares_soa_reply
+#define ares_parse_soa_reply   xares_parse_soa_reply
+
+struct ares_soa_reply {
+       char *nsname;
+       char *hostmaster;
+       uint32_t serial;
+       uint32_t refresh;
+       uint32_t retry;
+       uint32_t expire;
+       uint32_t minttl;
+};
+
+static void xares_free_soa(struct ares_soa_reply *soa)
+{
+       if (soa) {
+               if (soa->nsname)
+                       free(soa->nsname);
+               if (soa->hostmaster)
+                       free(soa->hostmaster);
+               free(soa);
+       }
+}
+
+/*
+ * Full SOA reply packet structure (rfc1035)
+ *
+ * 1) header
+ *   id:16, flags:16, qdcount:16, ancount:16, nscount:16, arcount:16
+ *
+ * 2) query (qdcount)
+ *   qname:name, qtype:16, qclass:16
+ *
+ * 3) answer (ancount)
+ *   name:name, type:16, class:16, ttl:32, rdlength:16
+ *
+ * 3.1) soa rdata
+ *   nsname:name, hostmaster:name,
+ *   serial:32, refresh:32, retry:32, expire:32, minimum:32
+ *
+ * 4) authority (nscount) - ignored
+ *
+ * 5) additional (arcount) - ignored
+ */
+static int ares_parse_soa_reply(const unsigned char *abuf, int alen, struct ares_soa_reply **soa_p)
+{
+       const unsigned char *aptr;
+       long len;
+       char *qname = NULL, *rr_name = NULL;
+       struct ares_soa_reply *soa = NULL;
+       int qdcount, ancount;
+       int status;
+
+       if (alen < NS_HFIXEDSZ)
+               return ARES_EBADRESP;
+
+       /* parse message header */
+       qdcount = DNS_HEADER_QDCOUNT(abuf);
+       ancount = DNS_HEADER_ANCOUNT(abuf);
+       if (qdcount != 1 || ancount != 1)
+               return ARES_EBADRESP;
+       aptr = abuf + NS_HFIXEDSZ;
+
+       /* allocate result struct */
+       soa = calloc(1, sizeof(*soa));
+       if (!soa)
+               return ARES_ENOMEM;
+
+       /* parse query */
+       status = ares_expand_name(aptr, abuf, alen, &qname, &len);
+       if (status != ARES_SUCCESS)
+               goto failed_stat;
+       aptr += len;
+
+       /* skip qtype & qclass */
+       if (aptr + NS_QFIXEDSZ > abuf + alen)
+               goto failed;
+       aptr += NS_QFIXEDSZ;
+
+       /* parse RR header */
+       status = ares_expand_name(aptr, abuf, alen, &rr_name, &len);
+       if (status != ARES_SUCCESS)
+               goto failed_stat;
+       aptr += len;
+
+       /* skip rr_type, rr_class, rr_ttl, rr_rdlen */
+       if (aptr + NS_RRFIXEDSZ > abuf + alen)
+               goto failed;
+       aptr += NS_RRFIXEDSZ;
+
+       /* nsname */
+       status = ares_expand_name(aptr, abuf, alen, &soa->nsname, &len);
+       if (status != ARES_SUCCESS)
+               goto failed_stat;
+       aptr += len;
+
+       /* hostmaster */
+       status = ares_expand_name(aptr, abuf, alen, &soa->hostmaster, &len);
+       if (status != ARES_SUCCESS)
+               goto failed_stat;
+       aptr += len;
+
+       /* integer fields */
+       if (aptr + 5*4 > abuf + alen)
+               goto failed;
+       soa->serial = DNS__32BIT(aptr + 0*4);
+       soa->refresh = DNS__32BIT(aptr + 1*4);
+       soa->retry = DNS__32BIT(aptr + 2*4);
+       soa->expire = DNS__32BIT(aptr + 3*4);
+       soa->minttl = DNS__32BIT(aptr + 4*4);
+
+       log_noise("Ares SOA result: qname=%s rr_name=%s serial=%u", qname, rr_name, soa->serial);
+
+       free(qname);
+       free(rr_name);
+
+       *soa_p = soa;
+
+       return ARES_SUCCESS;
+
+failed:
+       status = ARES_EBADRESP;
+
+failed_stat:
+       xares_free_soa(soa);
+       if (qname)
+               free(qname);
+       if (rr_name)
+               free(rr_name);
+       return (status == ARES_EBADNAME) ? ARES_EBADRESP : status;
+}
+
+#else /* HAVE_ARES_PARSE_SOA_REPLY */
+
+static void xares_free_soa(struct ares_soa_reply *soa)
+{
+       ares_free_data(soa);
+}
+
+#endif /* HAVE_ARES_PARSE_SOA_REPLY */
+
+
+/* called by c-ares on SOA reply */
+static void xares_soa_cb(void *arg, int status, int timeouts,
+                        unsigned char *abuf, int alen)
+{
+       struct DNSContext *ctx = arg;
+       struct XaresMeta *meta = ctx->edns;
+       struct ares_soa_reply *soa = NULL;
+
+       meta->got_events = 1;
+
+       log_noise("ares SOA result: %s", ares_strerror(status));
+       if (status != ARES_SUCCESS) {
+               got_zone_serial(ctx, NULL);
+               return;
+       }
+
+       status = ares_parse_soa_reply(abuf, alen, &soa);
+       if (status == ARES_SUCCESS) {
+               got_zone_serial(ctx, &soa->serial);
+       } else {
+               log_warning("ares_parse_soa: %s", ares_strerror(status));
+               got_zone_serial(ctx, NULL);
+       }
+
+       xares_free_soa(soa);
+}
+
+/* send SOA query */
+static int impl_query_soa_serial(struct DNSContext *ctx, const char *zonename)
+{
+       struct XaresMeta *meta = ctx->edns;
+
+       log_debug("dns: ares query SOA(%s)", zonename);
+       ares_search(meta->chan, zonename, ns_c_in, ns_t_soa,
+                   xares_soa_cb, ctx);
+
+       meta->got_events = 1;
+       return 0;
+}
+
+#endif /* USE_CARES */
+
+
 /*
  * Generic framework
  */
@@ -1143,3 +1633,8 @@ void adns_walk_zones(struct DNSContext *ctx, adns_walk_zone_f cb, void *arg)
        aatree_walk(&ctx->zone_tree, AA_WALK_IN_ORDER, walk_zone, &w);
 }
 
+void adns_per_loop(struct DNSContext *ctx)
+{
+       impl_per_loop(ctx);
+}
+
index cd8037766b2d17c67e162ff6fe8aaf3fa6470593..da80a35b843833f58422a25969b3a5991277e8f1 100644 (file)
@@ -635,6 +635,8 @@ static void main_loop_once(void)
        reuse_just_freed_objects();
        rescue_timers();
        per_loop_pooler_maint();
+
+       adns_per_loop(adns);
 }
 
 static void takeover_part1(void)