Merge branch 'master' of git+ssh://gnunet.org/gnunet
[oweals/gnunet.git] / src / dns / dnsstub.c
index e494d24a2ac2284745c69e6f50c470ce8e59bcb8..f9dc7a6966c13dc773fb8f7ee406190551748df6 100644 (file)
@@ -1,6 +1,6 @@
 /*
      This file is part of GNUnet.
-     (C) 2012 Christian Grothoff (and other contributing authors)
+     Copyright (C) 2012 GNUnet e.V.
 
      GNUnet is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published
@@ -14,8 +14,8 @@
 
      You should have received a copy of the GNU General Public License
      along with GNUnet; see the file COPYING.  If not, write to the
-     Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-     Boston, MA 02111-1307, USA.
+     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+     Boston, MA 02110-1301, USA.
 */
 /**
  * @file dns/dnsstub.c
@@ -24,6 +24,7 @@
  */
 #include "platform.h"
 #include "gnunet_util_lib.h"
+#include "gnunet_tun_lib.h"
 #include "gnunet_dnsstub_lib.h"
 
 /**
  */
 #define REQUEST_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5)
 
+/**
+ * Timeout for retrying DNS queries.
+ */
+#define DNS_RETRANSMIT_DELAY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 250)
+
 /**
  * How many DNS sockets do we open at most at the same time?
  * (technical socket maximum is this number x2 for IPv4+IPv6)
@@ -60,14 +66,19 @@ struct GNUNET_DNSSTUB_RequestSocket
   GNUNET_DNSSTUB_ResultCallback rc;
 
   /**
-   * Closure for 'rc'.
+   * Closure for @e rc.
    */
   void *rc_cls;
 
   /**
    * Task for reading from dnsout4 and dnsout6.
    */
-  GNUNET_SCHEDULER_TaskIdentifier read_task;
+  struct GNUNET_SCHEDULER_Task *read_task;
+
+  /**
+   * Task for retrying transmission of the query.
+   */
+  struct GNUNET_SCHEDULER_Task *retry_task;
 
   /**
    * When should this request time out?
@@ -80,10 +91,20 @@ struct GNUNET_DNSSTUB_RequestSocket
   struct sockaddr_storage addr;
 
   /**
-   * Number of bytes in 'addr'.
+   * Number of bytes in @e addr.
    */
   socklen_t addrlen;
 
+  /**
+   * Query we sent to @e addr.
+   */
+  void *request;
+
+  /**
+   * Number of bytes in @a request.
+   */
+  size_t request_len;
+
 };
 
 
@@ -100,15 +121,14 @@ struct GNUNET_DNSSTUB_Context
 
   /**
    * IP address to use for the DNS server if we are a DNS exit service
-   * (for VPN via mesh); otherwise NULL.
+   * (for VPN via cadet); otherwise NULL.
    */
   char *dns_exit;
 };
 
 
-
 /**
- * We're done with a GNUNET_DNSSTUB_RequestSocket, close it for now.
+ * We're done with a `struct GNUNET_DNSSTUB_RequestSocket`, close it for now.
  *
  * @param rs request socket to clean up
  */
@@ -125,10 +145,20 @@ cleanup_rs (struct GNUNET_DNSSTUB_RequestSocket *rs)
     GNUNET_NETWORK_socket_close (rs->dnsout6);
     rs->dnsout6 = NULL;
   }
-  if (GNUNET_SCHEDULER_NO_TASK != rs->read_task)
+  if (NULL != rs->read_task)
   {
     GNUNET_SCHEDULER_cancel (rs->read_task);
-    rs->read_task = GNUNET_SCHEDULER_NO_TASK;
+    rs->read_task = NULL;
+  }
+  if (NULL != rs->retry_task)
+  {
+    GNUNET_SCHEDULER_cancel (rs->retry_task);
+    rs->retry_task = NULL;
+  }
+  if (NULL != rs->request)
+  {
+    GNUNET_free (rs->request);
+    rs->request = NULL;
   }
 }
 
@@ -137,7 +167,7 @@ cleanup_rs (struct GNUNET_DNSSTUB_RequestSocket *rs)
  * Open source port for sending DNS requests
  *
  * @param af AF_INET or AF_INET6
- * @return GNUNET_OK on success
+ * @return #GNUNET_OK on success
  */
 static struct GNUNET_NETWORK_Handle *
 open_socket (int af)
@@ -185,11 +215,9 @@ open_socket (int af)
  * Read a DNS response from the (unhindered) UDP-Socket
  *
  * @param cls socket to read from
- * @param tc scheduler context (must be shutdown or read ready)
  */
 static void
-read_response (void *cls,
-              const struct GNUNET_SCHEDULER_TaskContext *tc);
+read_response (void *cls);
 
 
 /**
@@ -207,8 +235,22 @@ get_request_socket (struct GNUNET_DNSSTUB_Context *ctx,
   struct GNUNET_DNSSTUB_RequestSocket *rs;
   struct GNUNET_NETWORK_FDSet *rset;
 
-  rs = &ctx->sockets[GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
-                                              DNS_SOCKET_MAX)];
+  for (unsigned int i=0;i<256;i++)
+  {
+    rs = &ctx->sockets[GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
+                                                 DNS_SOCKET_MAX)];
+    if (NULL == rs->rc)
+      break;
+  }
+  if (NULL != rs->rc)
+  {
+    /* signal "failure" */
+    rs->rc (rs->rc_cls,
+            rs,
+            NULL,
+            0);
+    rs->rc = NULL;
+  }
   rs->timeout = GNUNET_TIME_relative_to_absolute (REQUEST_TIMEOUT);
   switch (af)
   {
@@ -223,39 +265,88 @@ get_request_socket (struct GNUNET_DNSSTUB_Context *ctx,
   default:
     return NULL;
   }
-  if (GNUNET_SCHEDULER_NO_TASK != rs->read_task)
+  if (NULL != rs->read_task)
   {
     GNUNET_SCHEDULER_cancel (rs->read_task);
-    rs->read_task = GNUNET_SCHEDULER_NO_TASK;
+    rs->read_task = NULL;
+  }
+  if (NULL != rs->retry_task)
+  {
+    GNUNET_SCHEDULER_cancel (rs->retry_task);
+    rs->retry_task = NULL;
+  }
+  if (NULL != rs->request)
+  {
+    GNUNET_free (rs->request);
+    rs->request = NULL;
   }
   if ( (NULL == rs->dnsout4) &&
        (NULL == rs->dnsout6) )
     return NULL;
   rset = GNUNET_NETWORK_fdset_create ();
   if (NULL != rs->dnsout4)
-    GNUNET_NETWORK_fdset_set (rset, rs->dnsout4);
+    GNUNET_NETWORK_fdset_set (rset,
+                              rs->dnsout4);
   if (NULL != rs->dnsout6)
-    GNUNET_NETWORK_fdset_set (rset, rs->dnsout6);
+    GNUNET_NETWORK_fdset_set (rset,
+                              rs->dnsout6);
   rs->read_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
                                               REQUEST_TIMEOUT,
                                               rset,
                                               NULL,
-                                              &read_response, rs);
+                                              &read_response,
+                                               rs);
   GNUNET_NETWORK_fdset_destroy (rset);
   return rs;
 }
 
 
+/**
+ * Task to (re)transmit the DNS query, possibly repeatedly until
+ * we succeed.
+ *
+ * @param cls our `struct GNUNET_DNSSTUB_RequestSocket *`
+ */
+static void
+transmit_query (void *cls)
+{
+  struct GNUNET_DNSSTUB_RequestSocket *rs = cls;
+  struct GNUNET_NETWORK_Handle *ret;
+
+  rs->retry_task = NULL;
+  ret = (NULL != rs->dnsout4) ? rs->dnsout4 : rs->dnsout6;
+  GNUNET_assert (NULL != ret);
+  if (GNUNET_SYSERR ==
+      GNUNET_NETWORK_socket_sendto (ret,
+                                   rs->request,
+                                   rs->request_len,
+                                   (struct sockaddr *) &rs->addr,
+                                   rs->addrlen))
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+               _("Failed to send DNS request to %s\n"),
+               GNUNET_a2s ((struct sockaddr *) &rs->addr,
+                            rs->addrlen));
+  else
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+               _("Sent DNS request to %s\n"),
+               GNUNET_a2s ((struct sockaddr *) &rs->addr,
+                            rs->addrlen));
+  rs->retry_task = GNUNET_SCHEDULER_add_delayed (DNS_RETRANSMIT_DELAY,
+                                                 &transmit_query,
+                                                 rs);
+}
+
+
 /**
  * Perform DNS resolution.
  *
  * @param ctx stub resolver to use
  * @param sa the socket address
- * @param sa_len the socket length
+ * @param sa_len the length of @a sa
  * @param request DNS request to transmit
- * @param request_len number of bytes in msg
+ * @param request_len number of bytes in @a request
  * @param rc function to call with result
- * @param rc_cls closure for 'rc'
+ * @param rc_cls closure for @a rc
  * @return socket used for the request, NULL on error
  */
 struct GNUNET_DNSSTUB_RequestSocket *
@@ -268,36 +359,22 @@ GNUNET_DNSSTUB_resolve (struct GNUNET_DNSSTUB_Context *ctx,
                        void *rc_cls)
 {
   struct GNUNET_DNSSTUB_RequestSocket *rs;
-  struct GNUNET_NETWORK_Handle *ret;
-  int af;
 
-  af = sa->sa_family;
-  if (NULL == (rs = get_request_socket (ctx, af)))
+  if (NULL == (rs = get_request_socket (ctx,
+                                        sa->sa_family)))
     return NULL;
-  if (NULL != rs->dnsout4)
-    ret = rs->dnsout4;
-  else
-    ret = rs->dnsout6;
-  GNUNET_assert (NULL != ret);
-  memcpy (&rs->addr,
-         sa,
-         sa_len);
+  GNUNET_assert (NULL == rs->rc);
+  GNUNET_memcpy (&rs->addr,
+                 sa,
+                 sa_len);
   rs->addrlen = sa_len;
   rs->rc = rc;
   rs->rc_cls = rc_cls;
-  if (GNUNET_SYSERR ==
-      GNUNET_NETWORK_socket_sendto (ret,
-                                   request,
-                                   request_len,
-                                   sa,
-                                   sa_len))
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-               _("Failed to send DNS request to %s\n"),
-               GNUNET_a2s (sa, sa_len));
-  else
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-               _("Sent DNS request to %s\n"),
-               GNUNET_a2s (sa, sa_len));
+  rs->request = GNUNET_memdup (request,
+                               request_len);
+  rs->request_len = request_len;
+  rs->retry_task = GNUNET_SCHEDULER_add_now (&transmit_query,
+                                             rs);
   return rs;
 }
 
@@ -329,7 +406,9 @@ GNUNET_DNSSTUB_resolve2 (struct GNUNET_DNSSTUB_Context *ctx,
 
   memset (&v4, 0, sizeof (v4));
   memset (&v6, 0, sizeof (v6));
-  if (1 == inet_pton (AF_INET, ctx->dns_exit, &v4.sin_addr))
+  if (1 == inet_pton (AF_INET,
+                      ctx->dns_exit,
+                      &v4.sin_addr))
   {
     salen = sizeof (v4);
     v4.sin_family = AF_INET;
@@ -340,7 +419,9 @@ GNUNET_DNSSTUB_resolve2 (struct GNUNET_DNSSTUB_Context *ctx,
     sa = (struct sockaddr *) &v4;
     af = AF_INET;
   }
-  else if (1 == inet_pton (AF_INET6, ctx->dns_exit, &v6.sin6_addr))
+  else if (1 == inet_pton (AF_INET6,
+                           ctx->dns_exit,
+                           &v6.sin6_addr))
   {
     salen = sizeof (v6);
     v6.sin6_family = AF_INET6;
@@ -356,8 +437,10 @@ GNUNET_DNSSTUB_resolve2 (struct GNUNET_DNSSTUB_Context *ctx,
     GNUNET_break (0);
     return NULL;
   }
-  if (NULL == (rs = get_request_socket (ctx, af)))
+  if (NULL == (rs = get_request_socket (ctx,
+                                        af)))
     return NULL;
+  GNUNET_assert (NULL == rs->rc);
   if (NULL != rs->dnsout4)
     dnsout = rs->dnsout4;
   else
@@ -369,23 +452,23 @@ GNUNET_DNSSTUB_resolve2 (struct GNUNET_DNSSTUB_Context *ctx,
                ctx->dns_exit);
     return NULL;
   }
-  memcpy (&rs->addr,
-         sa,
-         salen);
+  GNUNET_memcpy (&rs->addr,
+                 sa,
+                 salen);
   rs->addrlen = salen;
   rs->rc = rc;
   rs->rc_cls = rc_cls;
   if (GNUNET_SYSERR ==
       GNUNET_NETWORK_socket_sendto (dnsout,
                                    request,
-                                   request_len, sa, salen))
+                                   request_len,
+                                    sa,
+                                    salen))
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                _("Failed to send DNS request to %s\n"),
                GNUNET_a2s (sa, salen));
   rs->timeout = GNUNET_TIME_relative_to_absolute (REQUEST_TIMEOUT);
-
   return rs;
-
 }
 
 
@@ -395,7 +478,7 @@ GNUNET_DNSSTUB_resolve2 (struct GNUNET_DNSSTUB_Context *ctx,
  *
  * @param rs request socket with callback details
  * @param dnsout socket to read from
- * @return GNUNET_OK on success, GNUNET_NO on drop, GNUNET_SYSERR on IO-errors (closed socket)
+ * @return #GNUNET_OK on success, #GNUNET_NO on drop, #GNUNET_SYSERR on IO-errors (closed socket)
  */
 static int
 do_dns_read (struct GNUNET_DNSSTUB_RequestSocket *rs,
@@ -408,7 +491,9 @@ do_dns_read (struct GNUNET_DNSSTUB_RequestSocket *rs,
   int len;
 
 #ifndef MINGW
-  if (0 != ioctl (GNUNET_NETWORK_get_fd (dnsout), FIONREAD, &len))
+  if (0 != ioctl (GNUNET_NETWORK_get_fd (dnsout),
+                  FIONREAD,
+                  &len))
   {
     /* conservative choice: */
     len = UINT16_MAX;
@@ -426,11 +511,14 @@ do_dns_read (struct GNUNET_DNSSTUB_RequestSocket *rs,
     addrlen = sizeof (addr);
     memset (&addr, 0, sizeof (addr));
     r = GNUNET_NETWORK_socket_recvfrom (dnsout,
-                                       buf, sizeof (buf),
-                                       (struct sockaddr*) &addr, &addrlen);
+                                       buf,
+                                        sizeof (buf),
+                                       (struct sockaddr*) &addr,
+                                        &addrlen);
     if (-1 == r)
     {
-      GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "recvfrom");
+      GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+                           "recvfrom");
       GNUNET_NETWORK_socket_close (dnsout);
       return GNUNET_SYSERR;
     }
@@ -438,14 +526,15 @@ do_dns_read (struct GNUNET_DNSSTUB_RequestSocket *rs,
     {
       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  _("Received DNS response that is too small (%u bytes)"),
-                 r);
+                 (unsigned int) r);
       return GNUNET_NO;
     }
     dns = (struct GNUNET_TUN_DnsHeader *) buf;
     if ( (addrlen != rs->addrlen) ||
-        (0 != memcmp (&rs->addr,
-                      &addr,
-                      addrlen)) ||
+        (GNUNET_YES !=
+          GNUNET_TUN_sockaddr_cmp ((struct sockaddr *) &rs->addr,
+                                   (struct sockaddr *) &addr,
+                                   GNUNET_YES)) ||
        (0 == GNUNET_TIME_absolute_get_remaining (rs->timeout).rel_value_us) )
     {
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -466,43 +555,59 @@ do_dns_read (struct GNUNET_DNSSTUB_RequestSocket *rs,
  * Read a DNS response from the (unhindered) UDP-Socket
  *
  * @param cls socket to read from
- * @param tc scheduler context (must be shutdown or read ready)
  */
 static void
-read_response (void *cls,
-              const struct GNUNET_SCHEDULER_TaskContext *tc)
+read_response (void *cls)
 {
   struct GNUNET_DNSSTUB_RequestSocket *rs = cls;
   struct GNUNET_NETWORK_FDSet *rset;
+  const struct GNUNET_SCHEDULER_TaskContext *tc;
 
-  rs->read_task = GNUNET_SCHEDULER_NO_TASK;
+  rs->read_task = NULL;
+  tc = GNUNET_SCHEDULER_get_task_context ();
   if (0 == (tc->reason & GNUNET_SCHEDULER_REASON_READ_READY))
   {
-    /* timeout or shutdown */
+    /* signal "failure" (from timeout) */
+    if (NULL != rs->rc)
+    {
+      rs->rc (rs->rc_cls,
+              rs,
+              NULL,
+              0);
+      rs->rc = NULL;
+    }
+    /* timeout */
     cleanup_rs (rs);
     return;
   }
   /* read and process ready sockets */
   if ((NULL != rs->dnsout4) &&
-      (GNUNET_NETWORK_fdset_isset (tc->read_ready, rs->dnsout4)) &&
-      (GNUNET_SYSERR == do_dns_read (rs, rs->dnsout4)))
+      (GNUNET_NETWORK_fdset_isset (tc->read_ready,
+                                   rs->dnsout4)) &&
+      (GNUNET_SYSERR == do_dns_read (rs,
+                                     rs->dnsout4)))
     rs->dnsout4 = NULL;
   if ((NULL != rs->dnsout6) &&
-      (GNUNET_NETWORK_fdset_isset (tc->read_ready, rs->dnsout6)) &&
-      (GNUNET_SYSERR == do_dns_read (rs, rs->dnsout6)))
+      (GNUNET_NETWORK_fdset_isset (tc->read_ready,
+                                   rs->dnsout6)) &&
+      (GNUNET_SYSERR == do_dns_read (rs,
+                                     rs->dnsout6)))
     rs->dnsout6 = NULL;
 
   /* re-schedule read task */
   rset = GNUNET_NETWORK_fdset_create ();
   if (NULL != rs->dnsout4)
-    GNUNET_NETWORK_fdset_set (rset, rs->dnsout4);
+    GNUNET_NETWORK_fdset_set (rset,
+                              rs->dnsout4);
   if (NULL != rs->dnsout6)
-    GNUNET_NETWORK_fdset_set (rset, rs->dnsout6);
+    GNUNET_NETWORK_fdset_set (rset,
+                              rs->dnsout6);
   rs->read_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
                                               GNUNET_TIME_absolute_get_remaining (rs->timeout),
                                               rset,
                                               NULL,
-                                              &read_response, rs);
+                                              &read_response,
+                                               rs);
   GNUNET_NETWORK_fdset_destroy (rset);
 }
 
@@ -530,7 +635,7 @@ GNUNET_DNSSTUB_start (const char *dns_ip)
 {
   struct GNUNET_DNSSTUB_Context *ctx;
 
-  ctx = GNUNET_malloc (sizeof (struct GNUNET_DNSSTUB_Context));
+  ctx = GNUNET_new (struct GNUNET_DNSSTUB_Context);
   if (NULL != dns_ip)
     ctx->dns_exit = GNUNET_strdup (dns_ip);
   return ctx;