lots of useless recvfrom() calls

Lennert Buytenhek buytenh at wantstofly.org
Thu Jun 7 10:31:35 MSK 2018


On Tue, May 08, 2018 at 12:12:54PM +0300, Lennert Buytenhek wrote:

> > > Also, looping until EAGAIN makes your application somewhat susceptible
> > > to being DoSed.  (The event polling framework I use spends a lot of
> > > effort to make sure that all event sources are handled fairly, and that
> > > no one event source can starve out processing of other event sources.)
> > 
> > This is a good point. Maybe limiting number of packets to process to some
> > "reasonable" number, such as 5? :) Still, edge-triggering notification is
> > interesting here.
> > 
> > > If udns should work with edge-triggered event notification frameworks,
> > > then perhaps we could add a new function, say, dns_ioevent_single(),
> > > that only processes a single packet for this dns_ctx, and then have
> > > dns_ioevent() just call the _single() version in a loop until EAGAIN,
> > > or something like that?
> > 
> > A new function sounds good.
> > 
> > Also, if the number of system calls really is a concern, maybe using
> > recvmmsg() instead of recvfrom(), on linux, will be a good idea.
> 
> Sounds good!  But it's probably worth microbenchmarking to see how
> much slower recvmmsg() is compared to recvfrom() for the case where
> there is one reply, I'll do this if you don't beat me to it.

I (hackily) implemented mmsg support, patches are attached.  (I had
to rename sockaddr_ns because recvmmsg() requires -D_GNU_SOURCE, and
enabling _GNU_SOURCE also defines a sockaddr_ns.)  With this patchset
there are three ioevent functions:

* dns_ioevent(): loop until EAGAIN
* dns_ioevent_once(): recvfrom() only once from the socket
* dns_ioevent_mmsg(): recvmmsg() N (by default 5) packets at a time,
  and loop until a recvmmsg() call returns fewer than N packets

I modified the earlier bench.c and I compared the performance of _once()
versus _mmsg() over several thousand 15-second 1-parallel-query runs,
and that gives me:

===
$ ministat -c 99.5 -w 73 -q earlyout.user mmsg.user
x earlyout.user
+ mmsg.user
    N           Min           Max        Median           Avg        Stddev
x 6228          0.08          1.87          0.53     0.5273571    0.11094749
+ 6184          0.09          1.09          0.54    0.53886158      0.105258
Difference at 99.5% confidence
        0.0115045 +/- 0.00599926
        2.18154% +/- 1.13761%
        (Student's t, pooled s = 0.10815)
$ ministat -c 99.5 -w 73 -q earlyout.system mmsg.system
x earlyout.system
+ mmsg.system
    N           Min           Max        Median           Avg        Stddev
x 6228          0.32          4.72          1.45       1.43092    0.31032514
+ 6184          0.36          3.97          1.53     1.5091882    0.31004362
Difference at 99.5% confidence
        0.0782682 +/- 0.0172064
        5.46978% +/- 1.20247%
        (Student's t, pooled s = 0.310185)
$
===

So user time is up by ~2% and system time is up by ~5.5% for recvmmsg()
versus recvfrom() for the case where all recv*() calls return only 1
packet.  recvmmsg() doesn't look interesting from this point of view,
or at least, not for level triggered poll methods, where _once() seems
like the best option.

Maybe the more interesting comparison would be dns_ioevent() versus
dns_ioevent_mmsg(), for the edge triggered poll loop case, because
recvmmsg() does allow avoiding the useless final EAGAIN recvfrom()
for every poll iteration.
-------------- next part --------------
>From c74aceda5e62f84f7030daf784cf9d624012dcad Mon Sep 17 00:00:00 2001
From: Lennert Buytenhek <buytenh at wantstofly.org>
Date: Mon, 4 Jun 2018 12:13:51 +0300
Subject: [PATCH 1/5] Split reply processing code out of dns_ioevent().

---
 udns_resolver.c | 160 +++++++++++++++++++++++++-----------------------
 1 file changed, 83 insertions(+), 77 deletions(-)

diff --git a/udns_resolver.c b/udns_resolver.c
index b8f899a..fd31aa8 100644
--- a/udns_resolver.c
+++ b/udns_resolver.c
@@ -719,7 +719,7 @@ static void dns_newid(struct dns_ctx *ctx, struct dns_query *q) {
   q->dnsq_try = 0;
   q->dnsq_servi = 0;
   /*XXX probably should keep dnsq_servnEDNS0 bits?
-   * See also comments in dns_ioevent() about FORMERR case */
+   * See also comments in dns_handle_reply() about FORMERR case */
   q->dnsq_servwait = q->dnsq_servskip = q->dnsq_servnEDNS0 = 0;
 }
 
@@ -853,9 +853,10 @@ dns_send(struct dns_ctx *ctx, struct dns_query *q, time_t now) {
         !dns_find_serv(ctx, q)) {
       /* no more servers and tries, fail the query */
       /* return TEMPFAIL even when searching: no more tries for this
-       * searchlist, and no single definitive reply (handled in dns_ioevent()
-       * in NOERROR or NXDOMAIN cases) => all nameservers failed to process
-       * current search list element, so we don't know whenever the name exists.
+       * searchlist, and no single definitive reply (handled in
+       * dns_handle_reply() in NOERROR or NXDOMAIN cases) => all nameservers
+       * failed to process current search list element, so we don't know
+       * whenever the name exists.
        */
       dns_end_query(ctx, q, DNS_E_TEMPFAIL, 0);
       return;
@@ -947,77 +948,33 @@ dns_submit_p(struct dns_ctx *ctx,
     dns_submit_dn(ctx, ctx->dnsc_pbuf, qcls, qtyp, flags, parse, cbck, data);
 }
 
-/* process readable fd condition.
- * To be usable in edge-triggered environment, the routine
- * should consume all input so it should loop over.
- * Note it isn't really necessary to loop here, because
- * an application may perform the loop just fine by it's own,
- * but in this case we should return some sensitive result,
- * to indicate when to stop calling and error conditions.
- * Note also we may encounter all sorts of recvfrom()
- * errors which aren't fatal, and at the same time we may
- * loop forever if an error IS fatal.
- */
-void dns_ioevent(struct dns_ctx *ctx, time_t now) {
-  int r;
-  unsigned servi;
-  struct dns_query *q;
+static void dns_handle_reply(struct dns_ctx *ctx, time_t now, void *buf,
+			     int r, struct sockaddr *src_addr, socklen_t slen)
+{
   dnsc_t *pbuf;
   dnscc_t *pend, *pcur;
+  union sockaddr_ns *sns;
+  struct dns_query *q;
+  unsigned servi;
   void *result;
-  union sockaddr_ns sns;
-  socklen_t slen;
-
-  SETCTX(ctx);
-  if (!CTXOPEN(ctx))
-    return;
-  dns_assert_ctx(ctx);
-  pbuf = ctx->dnsc_pbuf;
-
-  if (!now) now = time(NULL);
-
-again: /* receive the reply */
-
-  slen = sizeof(sns);
-  r = recvfrom(ctx->dnsc_udpsock, (void*)pbuf, ctx->dnsc_udpbuf,
-               MSG_DONTWAIT, &sns.sa, &slen);
-  if (r < 0) {
-    /*XXX just ignore recvfrom() errors for now.
-     * in the future it may be possible to determine which
-     * query failed and requeue it.
-     * Note there may be various error conditions, triggered
-     * by both local problems and remote problems.  It isn't
-     * quite trivial to determine whenever an error is local
-     * or remote.  On local errors, we should stop, while
-     * remote errors should be ignored (for now anyway).
-     */
-#ifdef WINDOWS
-    if (WSAGetLastError() == WSAEWOULDBLOCK)
-#else
-    if (errno == EAGAIN)
-#endif
-    {
-      dns_request_utm(ctx, now);
-      return;
-    }
-    goto again;
-  }
 
+  pbuf = buf;
   pend = pbuf + r;
   pcur = dns_payload(pbuf);
+  sns = (union sockaddr_ns *)src_addr;
 
   /* check reply header */
   if (pcur > pend || dns_numqd(pbuf) > 1 || dns_opcode(pbuf) != 0) {
-    DNS_DBG(ctx, -1/*bad reply*/, &sns.sa, slen, pbuf, r);
-    goto again;
+    DNS_DBG(ctx, -1/*bad reply*/, &sns->sa, slen, pbuf, r);
+    return;
   }
 
   /* find the matching query, by qID */
   for (q = ctx->dnsc_qactive.head; ; q = q->dnsq_next) {
     if (!q) {
       /* no more requests: old reply? */
-      DNS_DBG(ctx, -5/*no matching query*/, &sns.sa, slen, pbuf, r);
-      goto again;
+      DNS_DBG(ctx, -5/*no matching query*/, &sns->sa, slen, pbuf, r);
+      return;
     }
     if (pbuf[DNS_H_QID1] == q->dnsq_id[0] &&
         pbuf[DNS_H_QID2] == q->dnsq_id[1])
@@ -1030,22 +987,22 @@ again: /* receive the reply */
     dnsc_t dn[DNS_MAXDN];
     if (dns_getdn(pbuf, &pcur, pend, dn, sizeof(dn)) < 0 ||
         pcur + 4 > pend) {
-      DNS_DBG(ctx, -1/*bad reply*/, &sns.sa, slen, pbuf, r);
-      goto again;
+      DNS_DBG(ctx, -1/*bad reply*/, &sns->sa, slen, pbuf, r);
+      return;
     }
     if (!dns_dnequal(dn, q->dnsq_dn) ||
         memcmp(pcur, q->dnsq_typcls, 4) != 0) {
       /* not this query */
-      DNS_DBG(ctx, -5/*no matching query*/, &sns.sa, slen, pbuf, r);
-      goto again;
+      DNS_DBG(ctx, -5/*no matching query*/, &sns->sa, slen, pbuf, r);
+      return;
     }
     /* here, query match, and pcur points past qDN in query section in pbuf */
   }
   /* if no numqd, we only allow FORMERR rcode */
   else if (dns_rcode(pbuf) != DNS_R_FORMERR) {
     /* treat it as bad reply if !FORMERR */
-    DNS_DBG(ctx, -1/*bad reply*/, &sns.sa, slen, pbuf, r);
-    goto again;
+    DNS_DBG(ctx, -1/*bad reply*/, &sns->sa, slen, pbuf, r);
+    return;
   }
   else {
     /* else it's FORMERR, handled below */
@@ -1053,16 +1010,16 @@ again: /* receive the reply */
 
   /* find server */
 #ifdef HAVE_IPv6
-  if (sns.sa.sa_family == AF_INET6 && slen >= sizeof(sns.sin6)) {
+  if (sns->sa.sa_family == AF_INET6 && slen >= sizeof(sns->sin6)) {
     for(servi = 0; servi < ctx->dnsc_nserv; ++servi)
-      if (sin6_eq(ctx->dnsc_serv[servi].sin6, sns.sin6))
+      if (sin6_eq(ctx->dnsc_serv[servi].sin6, sns->sin6))
         break;
   }
   else
 #endif
-  if (sns.sa.sa_family == AF_INET && slen >= sizeof(sns.sin)) {
+  if (sns->sa.sa_family == AF_INET && slen >= sizeof(sns->sin)) {
     for(servi = 0; servi < ctx->dnsc_nserv; ++servi)
-      if (sin_eq(ctx->dnsc_serv[servi].sin, sns.sin))
+      if (sin_eq(ctx->dnsc_serv[servi].sin, sns->sin))
         break;
   }
   else
@@ -1071,13 +1028,13 @@ again: /* receive the reply */
   /* check if we expect reply from this server.
    * Note we can receive reply from first try if we're already at next */
   if (!(q->dnsq_servwait & (1 << servi))) { /* if ever asked this NS */
-    DNS_DBG(ctx, -2/*wrong server*/, &sns.sa, slen, pbuf, r);
-    goto again;
+    DNS_DBG(ctx, -2/*wrong server*/, &sns->sa, slen, pbuf, r);
+    return;
   }
 
   /* we got (some) reply for our query */
 
-  DNS_DBGQ(ctx, q, 0, &sns.sa, slen, pbuf, r);
+  DNS_DBGQ(ctx, q, 0, &sns->sa, slen, pbuf, r);
   q->dnsq_servwait &= ~(1 << servi);	/* don't expect reply from this serv */
 
   /* process the RCODE */
@@ -1125,7 +1082,7 @@ again: /* receive the reply */
       memcpy(result, pbuf, r);
       dns_end_query(ctx, q, r, result);
     }
-    goto again;
+    return;
 
   case DNS_R_NXDOMAIN:	/* Non-existing domain. */
     if (dns_next_srch(ctx, q))
@@ -1136,7 +1093,7 @@ again: /* receive the reply */
        * if we've seen it before, or NXDOMAIN if not. */
       dns_end_query(ctx, q,
            q->dnsq_flags & DNS_SEEN_NODATA ? DNS_E_NODATA : DNS_E_NXDOMAIN, 0);
-    goto again;
+    return;
 
   case DNS_R_FORMERR:
   case DNS_R_NOTIMPL:
@@ -1155,7 +1112,7 @@ again: /* receive the reply */
        */
       q->dnsq_servnEDNS0 |= 1 << servi;
       dns_send_this(ctx, q, servi, now);
-      goto again;
+      return;
     }
     /* else we handle it the same as SERVFAIL etc */
 
@@ -1195,8 +1152,57 @@ again: /* receive the reply */
   else {
     /* else don't do anything - not all servers replied yet */
   }
-  goto again;
+}
+
+/* process readable fd condition.
+ * To be usable in edge-triggered environment, the routine
+ * should consume all input so it should loop over.
+ * Note it isn't really necessary to loop here, because
+ * an application may perform the loop just fine by it's own,
+ * but in this case we should return some sensitive result,
+ * to indicate when to stop calling and error conditions.
+ * Note also we may encounter all sorts of recvfrom()
+ * errors which aren't fatal, and at the same time we may
+ * loop forever if an error IS fatal.
+ */
+void dns_ioevent(struct dns_ctx *ctx, time_t now) {
+  SETCTX(ctx);
+  if (!CTXOPEN(ctx))
+    return;
+  dns_assert_ctx(ctx);
+
+  if (!now) now = time(NULL);
+
+  while (1) {
+    union sockaddr_ns sns;
+    socklen_t slen;
+    int r;
+
+    slen = sizeof(sns);
+    r = recvfrom(ctx->dnsc_udpsock, (void*)ctx->dnsc_pbuf, ctx->dnsc_udpbuf,
+                 MSG_DONTWAIT, &sns.sa, &slen);
+    if (r < 0) {
+      /*XXX just ignore recvfrom() errors for now.
+       * in the future it may be possible to determine which
+       * query failed and requeue it.
+       * Note there may be various error conditions, triggered
+       * by both local problems and remote problems.  It isn't
+       * quite trivial to determine whenever an error is local
+       * or remote.  On local errors, we should stop, while
+       * remote errors should be ignored (for now anyway).
+       */
+#ifdef WINDOWS
+      if (WSAGetLastError() == WSAEWOULDBLOCK)
+#else
+      if (errno == EAGAIN)
+#endif
+        break;
+      continue;
+    }
+    dns_handle_reply(ctx, now, ctx->dnsc_pbuf, r, &sns.sa, slen);
+  }
 
+  dns_request_utm(ctx, now);
 }
 
 /* handle all timeouts */
-- 
2.17.1

-------------- next part --------------
>From 179ffec488d9949de9cd98bd4c652fc1c6c4e700 Mon Sep 17 00:00:00 2001
From: Lennert Buytenhek <buytenh at wantstofly.org>
Date: Mon, 4 Jun 2018 12:23:28 +0300
Subject: [PATCH 2/5] Get rid of ctx->dnsc_pbuf.

---
 udns_resolver.c | 41 ++++++++++++++++++-----------------------
 1 file changed, 18 insertions(+), 23 deletions(-)

diff --git a/udns_resolver.c b/udns_resolver.c
index fd31aa8..bb92647 100644
--- a/udns_resolver.c
+++ b/udns_resolver.c
@@ -168,7 +168,6 @@ struct dns_ctx {		/* resolver context */
   int dnsc_udpsock;			/* UDP socket */
   struct dns_qlist dnsc_qactive;	/* active list sorted by deadline */
   int dnsc_nactive;			/* number entries in dnsc_qactive */
-  dnsc_t *dnsc_pbuf;			/* packet buffer (udpbuf size) */
   int dnsc_qstatus;			/* last query status value */
 };
 
@@ -422,9 +421,6 @@ void dns_close(struct dns_ctx *ctx) {
     if (ctx->dnsc_udpsock >= 0)
       closesocket(ctx->dnsc_udpsock);
     ctx->dnsc_udpsock = -1;
-    if (ctx->dnsc_pbuf)
-      free(ctx->dnsc_pbuf);
-    ctx->dnsc_pbuf = NULL;
     q = ctx->dnsc_qactive.head;
     while((p = q) != NULL) {
       q = q->dnsq_next;
@@ -463,7 +459,6 @@ struct dns_ctx *dns_new(const struct dns_ctx *copy) {
   ctx->dnsc_udpsock = -1;
   qlist_init(&ctx->dnsc_qactive);
   ctx->dnsc_nactive = 0;
-  ctx->dnsc_pbuf = NULL;
   ctx->dnsc_qstatus = 0;
   ctx->dnsc_srchend = ctx->dnsc_srchbuf +
     (copy->dnsc_srchend - copy->dnsc_srchbuf);
@@ -567,13 +562,6 @@ int dns_open(struct dns_ctx *ctx) {
     return -1;
   }
 #endif	/* WINDOWS */
-  /* allocate the packet buffer */
-  if ((ctx->dnsc_pbuf = malloc(ctx->dnsc_udpbuf)) == NULL) {
-    closesocket(sock);
-    ctx->dnsc_qstatus = DNS_E_NOMEM;
-    errno = ENOMEM;
-    return -1;
-  }
 
   ctx->dnsc_udpsock = sock;
   dns_request_utm(ctx, 0);
@@ -766,11 +754,14 @@ static int dns_find_serv(const struct dns_ctx *ctx, struct dns_query *q) {
 static int
 dns_send_this(struct dns_ctx *ctx, struct dns_query *q,
               unsigned servi, time_t now) {
+  dnsc_t *pbuf;
   unsigned qlen;
   unsigned tries;
 
+  pbuf = alloca(ctx->dnsc_udpbuf);
+
   { /* format the query buffer */
-    dnsc_t *p = ctx->dnsc_pbuf;
+    dnsc_t *p = pbuf;
     memset(p, 0, DNS_HSIZE);
     if (!(q->dnsq_flags & DNS_NORD)) p[DNS_H_F1] |= DNS_HF1_RD;
     if (q->dnsq_flags & DNS_AAONLY) p[DNS_H_F1] |= DNS_HF1_AA;
@@ -793,15 +784,15 @@ dns_send_this(struct dns_ctx *ctx, struct dns_query *q,
       memset(p, 0, 2+2+2);
       if (q->dnsq_flags & DNS_SET_DO) p[2] |= DNS_EF1_DO;
       p += 2+2+2;
-      ctx->dnsc_pbuf[DNS_H_ARCNT2] = 1;
+      pbuf[DNS_H_ARCNT2] = 1;
     }
-    qlen = p - ctx->dnsc_pbuf;
+    qlen = p - pbuf;
     assert(qlen <= ctx->dnsc_udpbuf);
   }
 
   /* send the query */
   tries = 10;
-  while (sendto(ctx->dnsc_udpsock, (void*)ctx->dnsc_pbuf, qlen, 0,
+  while (sendto(ctx->dnsc_udpsock, (void*)pbuf, qlen, 0,
                 &ctx->dnsc_serv[servi].sa, ctx->dnsc_salen) < 0) {
     /*XXX just ignore the sendto() error for now and try again.
      * In the future, it may be possible to retrieve the error code
@@ -814,8 +805,7 @@ dns_send_this(struct dns_ctx *ctx, struct dns_query *q,
     return -1;
   }
   DNS_DBGQ(ctx, q, 1,
-           &ctx->dnsc_serv[servi].sa, sizeof(union sockaddr_ns),
-           ctx->dnsc_pbuf, qlen);
+           &ctx->dnsc_serv[servi].sa, sizeof(union sockaddr_ns), pbuf, qlen);
   q->dnsq_servwait |= 1 << servi;	/* expect reply from this ns */
 
   q->dnsq_deadline = now +
@@ -936,16 +926,17 @@ struct dns_query *
 dns_submit_p(struct dns_ctx *ctx,
              const char *name, int qcls, int qtyp, int flags,
              dns_parse_fn *parse, dns_query_fn *cbck, void *data) {
+  dnsc_t *pbuf;
   int isabs;
   SETCTXOPEN(ctx);
-  if (dns_ptodn(name, 0, ctx->dnsc_pbuf, DNS_MAXDN, &isabs) <= 0) {
+  pbuf = alloca(ctx->dnsc_udpbuf);
+  if (dns_ptodn(name, 0, pbuf, DNS_MAXDN, &isabs) <= 0) {
     ctx->dnsc_qstatus = DNS_E_BADQUERY;
     return NULL;
   }
   if (isabs)
     flags |= DNS_NOSRCH;
-  return
-    dns_submit_dn(ctx, ctx->dnsc_pbuf, qcls, qtyp, flags, parse, cbck, data);
+  return dns_submit_dn(ctx, pbuf, qcls, qtyp, flags, parse, cbck, data);
 }
 
 static void dns_handle_reply(struct dns_ctx *ctx, time_t now, void *buf,
@@ -1166,11 +1157,15 @@ static void dns_handle_reply(struct dns_ctx *ctx, time_t now, void *buf,
  * loop forever if an error IS fatal.
  */
 void dns_ioevent(struct dns_ctx *ctx, time_t now) {
+  dnsc_t *pbuf;
+
   SETCTX(ctx);
   if (!CTXOPEN(ctx))
     return;
   dns_assert_ctx(ctx);
 
+  pbuf = alloca(ctx->dnsc_udpbuf);
+
   if (!now) now = time(NULL);
 
   while (1) {
@@ -1179,7 +1174,7 @@ void dns_ioevent(struct dns_ctx *ctx, time_t now) {
     int r;
 
     slen = sizeof(sns);
-    r = recvfrom(ctx->dnsc_udpsock, (void*)ctx->dnsc_pbuf, ctx->dnsc_udpbuf,
+    r = recvfrom(ctx->dnsc_udpsock, (void*)pbuf, ctx->dnsc_udpbuf,
                  MSG_DONTWAIT, &sns.sa, &slen);
     if (r < 0) {
       /*XXX just ignore recvfrom() errors for now.
@@ -1199,7 +1194,7 @@ void dns_ioevent(struct dns_ctx *ctx, time_t now) {
         break;
       continue;
     }
-    dns_handle_reply(ctx, now, ctx->dnsc_pbuf, r, &sns.sa, slen);
+    dns_handle_reply(ctx, now, pbuf, r, &sns.sa, slen);
   }
 
   dns_request_utm(ctx, now);
-- 
2.17.1

-------------- next part --------------
>From eeec6919b328b6beda96dd7491249c2560f9f5a5 Mon Sep 17 00:00:00 2001
From: Lennert Buytenhek <buytenh at wantstofly.org>
Date: Mon, 4 Jun 2018 12:30:53 +0300
Subject: [PATCH 3/5] Add dns_ioevent_once().

---
 udns.h          |  2 ++
 udns_resolver.c | 24 ++++++++++++++++++++++++
 2 files changed, 26 insertions(+)

diff --git a/udns.h b/udns.h
index 371e697..632dd48 100644
--- a/udns.h
+++ b/udns.h
@@ -460,6 +460,8 @@ dns_setstatus(struct dns_ctx *ctx, int status);
 /* handle I/O event on UDP socket */
 UDNS_API void
 dns_ioevent(struct dns_ctx *ctx, time_t now);
+UDNS_API void
+dns_ioevent_once(struct dns_ctx *ctx, time_t now);
 
 /* process any timeouts, return time in secounds to the
  * next timeout (or -1 if none) but not greather than maxwait */
diff --git a/udns_resolver.c b/udns_resolver.c
index bb92647..a1ed81f 100644
--- a/udns_resolver.c
+++ b/udns_resolver.c
@@ -1200,6 +1200,30 @@ void dns_ioevent(struct dns_ctx *ctx, time_t now) {
   dns_request_utm(ctx, now);
 }
 
+void dns_ioevent_once(struct dns_ctx *ctx, time_t now) {
+  dnsc_t *pbuf;
+  union sockaddr_ns sns;
+  socklen_t slen;
+  int r;
+
+  SETCTX(ctx);
+  if (!CTXOPEN(ctx))
+    return;
+  dns_assert_ctx(ctx);
+
+  pbuf = alloca(ctx->dnsc_udpbuf);
+
+  if (!now) now = time(NULL);
+
+  slen = sizeof(sns);
+  r = recvfrom(ctx->dnsc_udpsock, (void*)pbuf, ctx->dnsc_udpbuf,
+               MSG_DONTWAIT, &sns.sa, &slen);
+  if (r >= 0)
+    dns_handle_reply(ctx, now, pbuf, r, &sns.sa, slen);
+
+  dns_request_utm(ctx, now);
+}
+
 /* handle all timeouts */
 int dns_timeouts(struct dns_ctx *ctx, int maxwait, time_t now) {
   /* this is a hot routine */
-- 
2.17.1

-------------- next part --------------
>From 84af01fd8b4e45e7a5441b4a8e3fc438fed6b1d0 Mon Sep 17 00:00:00 2001
From: Lennert Buytenhek <buytenh at wantstofly.org>
Date: Mon, 4 Jun 2018 12:55:49 +0300
Subject: [PATCH 4/5] s/union sockaddr_ns/union sockaddr_udns/g.

---
 udns_resolver.c | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/udns_resolver.c b/udns_resolver.c
index a1ed81f..f2fdcd9 100644
--- a/udns_resolver.c
+++ b/udns_resolver.c
@@ -126,7 +126,7 @@ qlist_insert_after(struct dns_qlist *list,
     qlist_add_head(list, q);
 }
 
-union sockaddr_ns {
+union sockaddr_udns {
   struct sockaddr sa;
   struct sockaddr_in sin;
 #ifdef HAVE_IPv6
@@ -150,7 +150,7 @@ struct dns_ctx {		/* resolver context */
   unsigned dnsc_port;			/* default port (DNS_PORT) */
   unsigned dnsc_udpbuf;			/* size of UDP buffer */
   /* array of nameserver addresses */
-  union sockaddr_ns dnsc_serv[DNS_MAXSERV];
+  union sockaddr_udns dnsc_serv[DNS_MAXSERV];
   unsigned dnsc_nserv;			/* number of nameservers */
   unsigned dnsc_salen;			/* length of socket addresses */
   dnsc_t dnsc_srchbuf[1024];		/* buffer for searchlist */
@@ -229,7 +229,7 @@ enum {
 };
 
 int dns_add_serv(struct dns_ctx *ctx, const char *serv) {
-  union sockaddr_ns *sns;
+  union sockaddr_udns *sns;
   SETCTXFRESH(ctx);
   if (!serv)
     return (ctx->dnsc_nserv = 0);
@@ -478,7 +478,7 @@ int dns_open(struct dns_ctx *ctx) {
   int sock;
   unsigned i;
   int port;
-  union sockaddr_ns *sns;
+  union sockaddr_udns *sns;
 #ifdef HAVE_IPv6
   unsigned have_inet6 = 0;
 #endif
@@ -805,7 +805,7 @@ dns_send_this(struct dns_ctx *ctx, struct dns_query *q,
     return -1;
   }
   DNS_DBGQ(ctx, q, 1,
-           &ctx->dnsc_serv[servi].sa, sizeof(union sockaddr_ns), pbuf, qlen);
+           &ctx->dnsc_serv[servi].sa, sizeof(union sockaddr_udns), pbuf, qlen);
   q->dnsq_servwait |= 1 << servi;	/* expect reply from this ns */
 
   q->dnsq_deadline = now +
@@ -944,7 +944,7 @@ static void dns_handle_reply(struct dns_ctx *ctx, time_t now, void *buf,
 {
   dnsc_t *pbuf;
   dnscc_t *pend, *pcur;
-  union sockaddr_ns *sns;
+  union sockaddr_udns *sns;
   struct dns_query *q;
   unsigned servi;
   void *result;
@@ -952,7 +952,7 @@ static void dns_handle_reply(struct dns_ctx *ctx, time_t now, void *buf,
   pbuf = buf;
   pend = pbuf + r;
   pcur = dns_payload(pbuf);
-  sns = (union sockaddr_ns *)src_addr;
+  sns = (union sockaddr_udns *)src_addr;
 
   /* check reply header */
   if (pcur > pend || dns_numqd(pbuf) > 1 || dns_opcode(pbuf) != 0) {
@@ -1169,7 +1169,7 @@ void dns_ioevent(struct dns_ctx *ctx, time_t now) {
   if (!now) now = time(NULL);
 
   while (1) {
-    union sockaddr_ns sns;
+    union sockaddr_udns sns;
     socklen_t slen;
     int r;
 
@@ -1202,7 +1202,7 @@ void dns_ioevent(struct dns_ctx *ctx, time_t now) {
 
 void dns_ioevent_once(struct dns_ctx *ctx, time_t now) {
   dnsc_t *pbuf;
-  union sockaddr_ns sns;
+  union sockaddr_udns sns;
   socklen_t slen;
   int r;
 
-- 
2.17.1

-------------- next part --------------
>From 90181f03860a127ea9d3c63694b7c1211fe5e6b4 Mon Sep 17 00:00:00 2001
From: Lennert Buytenhek <buytenh at wantstofly.org>
Date: Mon, 4 Jun 2018 12:56:21 +0300
Subject: [PATCH 5/5] Add dns_ioevent_mmsg().

---
 udns.h          |  2 ++
 udns_resolver.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 68 insertions(+)

diff --git a/udns.h b/udns.h
index 632dd48..1145e66 100644
--- a/udns.h
+++ b/udns.h
@@ -462,6 +462,8 @@ UDNS_API void
 dns_ioevent(struct dns_ctx *ctx, time_t now);
 UDNS_API void
 dns_ioevent_once(struct dns_ctx *ctx, time_t now);
+UDNS_API void
+dns_ioevent_mmsg(struct dns_ctx *ctx, time_t now);
 
 /* process any timeouts, return time in secounds to the
  * next timeout (or -1 if none) but not greather than maxwait */
diff --git a/udns_resolver.c b/udns_resolver.c
index f2fdcd9..c42e290 100644
--- a/udns_resolver.c
+++ b/udns_resolver.c
@@ -28,6 +28,7 @@
 # include <winsock2.h>          /* includes <windows.h> */
 # include <ws2tcpip.h>          /* needed for struct in6_addr */
 #else
+# define _GNU_SOURCE
 # include <sys/types.h>
 # include <sys/socket.h>
 # include <netinet/in.h>
@@ -1224,6 +1225,71 @@ void dns_ioevent_once(struct dns_ctx *ctx, time_t now) {
   dns_request_utm(ctx, now);
 }
 
+#define NUMBUF	5
+
+void dns_ioevent_mmsg(struct dns_ctx *ctx, time_t now) {
+  dnsc_t *pbuf;
+
+  SETCTX(ctx);
+  if (!CTXOPEN(ctx))
+    return;
+  dns_assert_ctx(ctx);
+
+  pbuf = alloca(NUMBUF * ctx->dnsc_udpbuf);
+
+  if (!now) now = time(NULL);
+
+  while (1) {
+    struct mmsghdr mh[NUMBUF];
+    union sockaddr_udns sns[NUMBUF];
+    struct iovec iov[NUMBUF];
+    int i;
+    int r;
+
+    for (i = 0; i < NUMBUF; i++) {
+      mh[i].msg_hdr.msg_name = &sns[i];
+      mh[i].msg_hdr.msg_namelen = sizeof(sns[i]);
+      mh[i].msg_hdr.msg_iov = &iov[i];
+      mh[i].msg_hdr.msg_iovlen = 1;
+      mh[i].msg_hdr.msg_control = NULL;
+      mh[i].msg_hdr.msg_controllen = 0;
+
+      iov[i].iov_base = pbuf + (i * ctx->dnsc_udpbuf);
+      iov[i].iov_len = ctx->dnsc_udpbuf;
+    }
+
+    r = recvmmsg(ctx->dnsc_udpsock, mh, NUMBUF, MSG_DONTWAIT, NULL);
+    if (r < 0) {
+      /*XXX just ignore recvfrom() errors for now.
+       * in the future it may be possible to determine which
+       * query failed and requeue it.
+       * Note there may be various error conditions, triggered
+       * by both local problems and remote problems.  It isn't
+       * quite trivial to determine whenever an error is local
+       * or remote.  On local errors, we should stop, while
+       * remote errors should be ignored (for now anyway).
+       */
+#ifdef WINDOWS
+      if (WSAGetLastError() == WSAEWOULDBLOCK)
+#else
+      if (errno == EAGAIN)
+#endif
+        break;
+      continue;
+    }
+
+    for (i = 0; i < r; i++) {
+      dns_handle_reply(ctx, now, iov[i].iov_base, mh[i].msg_len,
+                       &sns[i].sa, mh[i].msg_hdr.msg_namelen);
+    }
+
+    if (r < NUMBUF)
+      break;
+  }
+
+  dns_request_utm(ctx, now);
+}
+
 /* handle all timeouts */
 int dns_timeouts(struct dns_ctx *ctx, int maxwait, time_t now) {
   /* this is a hot routine */
-- 
2.17.1



More information about the udns mailing list