-fix leak
[oweals/gnunet.git] / src / dns / gnunet-service-dns.c
index 5dc6641e74d7c3758137230ee42c32baae01b064..79beef83191d304621897166dbf755ab8a5714db 100644 (file)
 
 /**
  * @file dns/gnunet-service-dns.c
+ * @brief service to intercept and modify DNS queries (and replies) of this system
  * @author Christian Grothoff
+ *
+ * For "secure" interaction with the legacy DNS system, we permit
+ * replies only to arrive within a 5s window (and they must match
+ * ports, IPs and request IDs).  Furthermore, we let the OS pick a
+ * source port, opening up to 128 sockets per address family (IPv4 or
+ * IPv6).  Those sockets are closed if they are not in use for 5s
+ * (which means they will be freshly randomized afterwards).  For new
+ * requests, we pick a random slot in the array with 128 socket slots
+ * (and re-use an existing socket if the slot is still in use).  Thus
+ * each request will be given one of 128 random source ports, and the
+ * 128 random source ports will also change "often" (less often if the
+ * system is very busy, each time if we are mostly idle).  At the same
+ * time, the system will never use more than 256 UDP sockets.  
  */
 #include "platform.h"
 #include "gnunet_util_lib.h"
+#include "gnunet_applications.h"
 #include "gnunet_constants.h"
 #include "gnunet_protocols.h"
 #include "gnunet_signatures.h"
 #include "dns.h"
 #include "gnunet_dns_service.h"
+#include "gnunet_mesh_service.h"
 #include "gnunet_statistics_service.h"
 #include "gnunet_tun_lib.h"
 
 
+/**
+ * Timeout for an external (Internet-DNS) DNS resolution
+ */
+#define REQUEST_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5)
+
+/**
+ * How many DNS sockets do we open at most at the same time?
+ * (technical socket maximum is this number x2 for IPv4+IPv6)
+ */
+#define DNS_SOCKET_MAX 128
+
 /**
  * Phases each request goes through.
  */
@@ -108,6 +135,34 @@ struct ClientRecord
 };
 
 
+/**
+ * UDP socket we are using for sending DNS requests to the Internet.
+ */
+struct RequestSocket
+{
+  
+  /**
+   * UDP socket we use for this request for IPv4
+   */
+  struct GNUNET_NETWORK_Handle *dnsout4;
+
+  /**
+   * UDP socket we use for this request for IPv6
+   */
+  struct GNUNET_NETWORK_Handle *dnsout6;
+
+  /**
+   * Task for reading from dnsout4 and dnsout6.
+   */
+  GNUNET_SCHEDULER_TaskIdentifier read_task;
+
+  /**
+   * When should this socket be closed?
+   */
+  struct GNUNET_TIME_Absolute timeout;
+};
+
+
 /**
  * Entry we keep for each active request.
  */ 
@@ -126,6 +181,13 @@ struct RequestRecord
    */
   char *payload;
 
+  /**
+   * Socket we are using to transmit this request (must match if we receive
+   * a response).  Must NOT be freed as part of this request record (as it
+   * might be shared with other requests).
+   */
+  struct GNUNET_NETWORK_Handle *dnsout;
+
   /**
    * Source address of the original request (for sending response).
    */
@@ -136,6 +198,11 @@ struct RequestRecord
    */
   struct sockaddr_storage dst_addr;
 
+  /**
+   * When should this request time out?
+   */
+  struct GNUNET_TIME_Absolute timeout;
+
   /**
    * ID of this request, also basis for hashing.  Lowest 16 bit will
    * be our message ID when doing a global DNS request and our index
@@ -162,32 +229,64 @@ struct RequestRecord
 
 
 /**
- * The IPv4 UDP-Socket through which DNS-Resolves will be sent if they are not to be
- * sent through gnunet. The port of this socket will not be hijacked.
+ * State we keep for each DNS tunnel that terminates at this node.
  */
-static struct GNUNET_NETWORK_Handle *dnsout4;
+struct TunnelState
+{
 
-/**
- * The IPv6 UDP-Socket through which DNS-Resolves will be sent if they are not to be
- * sent through gnunet. The port of this socket will not be hijacked.
- */
-static struct GNUNET_NETWORK_Handle *dnsout6;
+  /**
+   * Associated MESH tunnel.
+   */
+  struct GNUNET_MESH_Tunnel *tunnel;
 
-/**
* Task for reading from dnsout4.
- */
-static GNUNET_SCHEDULER_TaskIdentifier read4_task;
+  /**
  * Active request for sending a reply.
  */
+  struct GNUNET_MESH_TransmitHandle *th;
 
-/**
- * Task for reading from dnsout6.
- */
-static GNUNET_SCHEDULER_TaskIdentifier read6_task;
+  /**
+   * DNS reply ready for transmission.
+   */
+  char *reply;
+
+  /**
+   * Socket we are using to transmit this request (must match if we receive
+   * a response).  Must NOT be freed as part of this request record (as it
+   * might be shared with other requests).
+   */
+  struct GNUNET_NETWORK_Handle *dnsout;
+
+  /**
+   * Address we sent the DNS request to.
+   */
+  struct sockaddr_storage addr;
+
+  /**
+   * When should this request time out?
+   */
+  struct GNUNET_TIME_Absolute timeout;
+
+  /**
+   * Number of bytes in 'addr'.
+   */
+  socklen_t addrlen;
+
+  /**
+   * Number of bytes in 'reply'.
+   */
+  size_t reply_length;
+
+  /**
+   * Original DNS request ID as used by the client.
+   */
+  uint16_t original_id;
+
+  /**
+   * DNS request ID that we used for forwarding.
+   */
+  uint16_t my_id;
+};
 
-/**
- * The port bound to the socket dnsout (and/or dnsout6).  We always (try) to bind
- * both sockets to the same port.
- */
-static uint16_t dnsoutport;
 
 /**
  * The configuration to use
@@ -207,7 +306,7 @@ static struct GNUNET_HELPER_Handle *hijacker;
 /**
  * Command-line arguments we are giving to the hijacker process.
  */
-static char *helper_argv[8];
+static char *helper_argv[7];
 
 /**
  * Head of DLL of clients we consult.
@@ -229,11 +328,59 @@ static struct GNUNET_SERVER_NotificationContext *nc;
  */
 static struct RequestRecord requests[UINT16_MAX + 1];
 
+/**
+ * Array of all open requests from tunnels.
+ */
+static struct TunnelState *tunnels[UINT16_MAX + 1];
+
+/**
+ * Array of all open sockets for DNS requests. 
+ */
+static struct RequestSocket sockets[DNS_SOCKET_MAX];
+
 /**
  * Generator for unique request IDs.
  */
 static uint64_t request_id_gen;
 
+/**
+ * IP address to use for the DNS server if we are a DNS exit service
+ * (for VPN via mesh); otherwise NULL.
+ */
+static char *dns_exit;
+
+/**
+ * Handle to the MESH service (for receiving DNS queries), or NULL 
+ * if we are not a DNS exit.
+ */
+static struct GNUNET_MESH_Handle *mesh;
+
+
+/**
+ * We're done with a RequestSocket, close it for now.
+ *
+ * @param rs request socket to clean up
+ */
+static void
+cleanup_rs (struct RequestSocket *rs)
+{
+  if (NULL != rs->dnsout4)
+  {
+    GNUNET_NETWORK_socket_close (rs->dnsout4);
+    rs->dnsout4 = NULL;
+  }
+  if (NULL != rs->dnsout6)
+  {
+    GNUNET_NETWORK_socket_close (rs->dnsout6);
+    rs->dnsout6 = NULL;
+  }
+  if (GNUNET_SCHEDULER_NO_TASK != rs->read_task)
+  {
+    GNUNET_SCHEDULER_cancel (rs->read_task);
+    rs->read_task = GNUNET_SCHEDULER_NO_TASK;
+  }
+}
+
 
 /**
  * We're done processing a DNS request, free associated memory.
@@ -266,37 +413,70 @@ cleanup_task (void *cls GNUNET_UNUSED,
 
   GNUNET_HELPER_stop (hijacker);
   hijacker = NULL;
-  for (i=0;i<8;i++)
+  for (i=0;i<7;i++)
     GNUNET_free_non_null (helper_argv[i]);
-  if (NULL != dnsout4)
-  {
-    GNUNET_NETWORK_socket_close (dnsout4);
-    dnsout4 = NULL;
-  }
-  if (GNUNET_SCHEDULER_NO_TASK != read4_task)
+  for (i=0;i<=UINT16_MAX;i++)
+    cleanup_rr (&requests[i]);
+  GNUNET_SERVER_notification_context_destroy (nc);
+  nc = NULL;
+  if (stats != NULL)
   {
-    GNUNET_SCHEDULER_cancel (read4_task);
-    read4_task = GNUNET_SCHEDULER_NO_TASK;
+    GNUNET_STATISTICS_destroy (stats, GNUNET_NO);
+    stats = NULL;
   }
-  if (NULL != dnsout6)
+  if (NULL != dns_exit)
   {
-    GNUNET_NETWORK_socket_close (dnsout6);
-    dnsout6 = NULL;
+    GNUNET_free (dns_exit);
+    dns_exit = NULL;
   }
-  if (GNUNET_SCHEDULER_NO_TASK != read6_task)
+}
+
+
+/**
+ * Open source port for sending DNS requests
+ *
+ * @param af AF_INET or AF_INET6
+ * @return GNUNET_OK on success
+ */ 
+static struct GNUNET_NETWORK_Handle *
+open_socket (int af)
+{
+  struct sockaddr_in a4;
+  struct sockaddr_in6 a6;
+  struct sockaddr *sa;
+  socklen_t alen;
+  struct GNUNET_NETWORK_Handle *ret;
+
+  ret = GNUNET_NETWORK_socket_create (af, SOCK_DGRAM, 0);
+  if (NULL == ret)
+    return NULL;
+  switch (af)
   {
-    GNUNET_SCHEDULER_cancel (read6_task);
-    read6_task = GNUNET_SCHEDULER_NO_TASK;
+  case AF_INET:
+    memset (&a4, 0, alen = sizeof (struct sockaddr_in));
+    sa = (struct sockaddr *) &a4;
+    break;
+  case AF_INET6:
+    memset (&a6, 0, alen = sizeof (struct sockaddr_in6));
+    sa = (struct sockaddr *) &a6;
+    break;
+  default:
+    GNUNET_break (0);
+    GNUNET_NETWORK_socket_close (ret);
+    return NULL;
   }
-  for (i=0;i<65536;i++)
-    cleanup_rr (&requests[i]);
-  GNUNET_SERVER_notification_context_destroy (nc);
-  nc = NULL;
-  if (stats != NULL)
+  sa->sa_family = af;
+  if (GNUNET_OK != GNUNET_NETWORK_socket_bind (ret,
+                                              sa, 
+                                              alen))
   {
-    GNUNET_STATISTICS_destroy (stats, GNUNET_YES);
-    stats = NULL;
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 
+               _("Could not bind to any port: %s\n"),
+               STRERROR (errno));
+    GNUNET_NETWORK_socket_close (ret);
+    return NULL;
   }
+  return ret;
 }
 
 
@@ -310,8 +490,8 @@ request_done (struct RequestRecord *rr)
 {
   struct GNUNET_MessageHeader *hdr;
   size_t reply_len;
-  uint16_t spt;
-  uint16_t dpt;
+  uint16_t source_port;
+  uint16_t destination_port;
 
   GNUNET_array_grow (rr->client_wait_list,
                     rr->client_wait_list_length,
@@ -351,7 +531,8 @@ request_done (struct RequestRecord *rr)
   {
     char buf[reply_len];
     size_t off;
-    uint32_t udp_crc_sum;
+    struct GNUNET_TUN_IPv4Header ip4;
+    struct GNUNET_TUN_IPv6Header ip6;
 
     /* first, GNUnet message header */
     hdr = (struct GNUNET_MessageHeader*) buf;
@@ -373,78 +554,38 @@ request_done (struct RequestRecord *rr)
     }
 
     /* now IP header */
-    udp_crc_sum = 0;    
     switch (rr->src_addr.ss_family)
     {
     case AF_INET:
       {
        struct sockaddr_in *src = (struct sockaddr_in *) &rr->src_addr;
        struct sockaddr_in *dst = (struct sockaddr_in *) &rr->dst_addr;
-       struct GNUNET_TUN_IPv4Header ip;
        
-       spt = dst->sin_port;
-       dpt = src->sin_port;
-       GNUNET_TUN_initialize_ipv4_header (&ip,
+       source_port = dst->sin_port;
+       destination_port = src->sin_port;
+       GNUNET_TUN_initialize_ipv4_header (&ip4,
                                           IPPROTO_UDP,
                                           reply_len - off - sizeof (struct GNUNET_TUN_IPv4Header),
                                           &dst->sin_addr,
                                           &src->sin_addr);
-
-
-
-       udp_crc_sum = GNUNET_CRYPTO_crc16_step (udp_crc_sum, 
-                                               &ip.source_address,
-                                               sizeof (struct in_addr) * 2);
-       {
-         uint16_t tmp;
-         
-         tmp = htons (IPPROTO_UDP);
-         udp_crc_sum = GNUNET_CRYPTO_crc16_step (udp_crc_sum, 
-                                                 &tmp, 
-                                                 sizeof (uint16_t));
-         tmp = htons (rr->payload_length + sizeof (struct GNUNET_TUN_UdpHeader));
-         udp_crc_sum = GNUNET_CRYPTO_crc16_step (udp_crc_sum, 
-                                                 &tmp, 
-                                                 sizeof (uint16_t));
-       }
-       memcpy (&buf[off], &ip, sizeof (ip));
-       off += sizeof (ip);
+       memcpy (&buf[off], &ip4, sizeof (ip4));
+       off += sizeof (ip4);
       }
       break;
     case AF_INET6:
       {
        struct sockaddr_in6 *src = (struct sockaddr_in6 *) &rr->src_addr;
        struct sockaddr_in6 *dst = (struct sockaddr_in6 *) &rr->dst_addr;
-       struct GNUNET_TUN_IPv6Header ip;
-
-       spt = dst->sin6_port;
-       dpt = src->sin6_port;
-       ip.traffic_class_h = 0;
-       ip.version  = 6; /* is there a named constant? I couldn't find one */
-       ip.traffic_class_l = 0;
-       ip.flow_label = 0;
-       ip.payload_length = htons ((uint16_t) reply_len);
-       ip.next_header = IPPROTO_UDP;
-       ip.hop_limit = 255; /* or lower? */
-       ip.source_address = dst->sin6_addr;
-       ip.destination_address = src->sin6_addr;
-       udp_crc_sum = GNUNET_CRYPTO_crc16_step (udp_crc_sum,
-                                               &ip.source_address, 
-                                               sizeof (struct in6_addr) * 2);
-       {
-         uint32_t tmp;
-         
-         tmp = htons (rr->payload_length + sizeof (struct GNUNET_TUN_UdpHeader));
-         udp_crc_sum = GNUNET_CRYPTO_crc16_step (udp_crc_sum, 
-                                                 &tmp, 
-                                                 sizeof (uint32_t));
-         tmp = htons (IPPROTO_UDP);
-         udp_crc_sum = GNUNET_CRYPTO_crc16_step (udp_crc_sum, 
-                                                 &tmp, 
-                                                 sizeof (uint32_t));
-       }
-       memcpy (&buf[off], &ip, sizeof (ip));
-       off += sizeof (ip);
+
+       source_port = dst->sin6_port;
+       destination_port = src->sin6_port;
+       GNUNET_TUN_initialize_ipv6_header (&ip6,
+                                          IPPROTO_UDP,
+                                          reply_len - sizeof (struct GNUNET_TUN_IPv6Header),
+                                          &dst->sin6_addr,
+                                          &src->sin6_addr);
+       memcpy (&buf[off], &ip6, sizeof (ip6));
+       off += sizeof (ip6);
       }
       break;
     default:
@@ -455,20 +596,23 @@ request_done (struct RequestRecord *rr)
     {
       struct GNUNET_TUN_UdpHeader udp;
 
-      udp.spt = spt;
-      udp.dpt = dpt;
+      udp.source_port = source_port;
+      udp.destination_port = destination_port;
       udp.len = htons (reply_len - off);
-      udp.crc = 0; 
-      udp_crc_sum = GNUNET_CRYPTO_crc16_step (udp_crc_sum, 
-                                             &udp, 
-                                             sizeof (udp));
-      udp_crc_sum = GNUNET_CRYPTO_crc16_step (udp_crc_sum, 
-                                             rr->payload,
-                                             rr->payload_length);
-      udp.crc = GNUNET_CRYPTO_crc16_finish (udp_crc_sum);
+      if (AF_INET == rr->src_addr.ss_family)
+       GNUNET_TUN_calculate_udp4_checksum (&ip4,
+                                           &udp,
+                                           rr->payload,
+                                           rr->payload_length);
+      else
+       GNUNET_TUN_calculate_udp6_checksum (&ip6,
+                                           &udp,
+                                           rr->payload,
+                                           rr->payload_length);
       memcpy (&buf[off], &udp, sizeof (udp));
       off += sizeof (udp);
     }
+
     /* now DNS payload */
     {
       memcpy (&buf[off], rr->payload, rr->payload_length);
@@ -522,6 +666,73 @@ send_request_to_client (struct RequestRecord *rr,
 }
 
 
+/**
+ * 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);
+
+
+/**
+ * Get a socket of the specified address family to send out a
+ * UDP DNS request to the Internet.  
+ *
+ * @param af desired address family
+ * @return NULL on error (given AF not "supported")
+ */
+static struct GNUNET_NETWORK_Handle *
+get_request_socket (int af)
+{
+  struct RequestSocket *rs;
+  struct GNUNET_NETWORK_FDSet *rset;
+  static struct GNUNET_NETWORK_Handle *ret;
+
+  rs = &sockets[GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, 
+                                         DNS_SOCKET_MAX)];
+  rs->timeout = GNUNET_TIME_relative_to_absolute (REQUEST_TIMEOUT);
+  switch (af)
+  {
+  case AF_INET:
+    if (NULL == rs->dnsout4)
+      rs->dnsout4 = open_socket (AF_INET);
+    ret = rs->dnsout4;
+    break;
+  case AF_INET6:
+    if (NULL == rs->dnsout6)
+      rs->dnsout6 = open_socket (AF_INET6);
+    ret = rs->dnsout6;
+    break;
+  default:
+    return NULL;
+  }  
+  if (GNUNET_SCHEDULER_NO_TASK != rs->read_task)
+  {
+    GNUNET_SCHEDULER_cancel (rs->read_task);
+    rs->read_task = GNUNET_SCHEDULER_NO_TASK;
+  }
+  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);
+  if (NULL != rs->dnsout6)
+    GNUNET_NETWORK_fdset_set (rset, rs->dnsout6);
+  rs->read_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
+                                              GNUNET_SCHEDULER_NO_TASK,
+                                              REQUEST_TIMEOUT,
+                                              rset,
+                                              NULL,
+                                              &read_response, rs);
+  GNUNET_NETWORK_fdset_destroy (rset);
+  return ret;
+}
+
+
 /**
  * A client has completed its processing for this
  * request.  Move on.
@@ -534,7 +745,6 @@ next_phase (struct RequestRecord *rr)
   struct ClientRecord *cr;
   int nz;
   unsigned int j;
-  struct GNUNET_NETWORK_Handle *dnsout;
   socklen_t salen;
 
   if (rr->phase == RP_DROP)
@@ -582,35 +792,34 @@ next_phase (struct RequestRecord *rr)
     next_phase (rr);
     return;
   case RP_QUERY:
-    rr->phase = RP_INTERNET_DNS;
     switch (rr->dst_addr.ss_family)
     {
     case AF_INET:
-      dnsout = dnsout4;
-      salen = sizeof (struct GNUNET_TUN_IPv4Header);
+      salen = sizeof (struct sockaddr_in);
       break;
     case AF_INET6:
-      dnsout = dnsout6;
-      salen = sizeof (struct GNUNET_TUN_IPv6Header);
+      salen = sizeof (struct sockaddr_in6);
       break;
     default:
-      GNUNET_break (0);
-      cleanup_rr (rr);
-      return;   
+      GNUNET_assert (0);
     }
-    if (NULL == dnsout)
+
+    rr->phase = RP_INTERNET_DNS;
+    rr->dnsout = get_request_socket (rr->dst_addr.ss_family);
+    if (NULL == rr->dnsout)
     {
       GNUNET_STATISTICS_update (stats,
-                               gettext_noop ("# DNS exit failed (address family not supported)"),
+                               gettext_noop ("# DNS exit failed (failed to open socket)"),
                                1, GNUNET_NO);
       cleanup_rr (rr);
       return;
     }
-    GNUNET_NETWORK_socket_sendto (dnsout,
+    GNUNET_NETWORK_socket_sendto (rr->dnsout,
                                  rr->payload,
                                  rr->payload_length,
                                  (struct sockaddr*) &rr->dst_addr,
                                  salen);
+    rr->timeout = GNUNET_TIME_relative_to_absolute (REQUEST_TIMEOUT);
     return;
   case RP_INTERNET_DNS:
     rr->phase = RP_MODIFY;
@@ -692,208 +901,200 @@ client_disconnect (void *cls, struct GNUNET_SERVER_Client *client)
 
 
 /**
- * Read a DNS response from the (unhindered) UDP-Socket
+ * We got a reply from DNS for a request of a MESH tunnel.  Send it
+ * via the tunnel (after changing the request ID back).
  *
- * @param cls socket to read from
- * @param tc scheduler context (must be shutdown or read ready)
+ * @param cls the 'struct TunnelState'
+ * @param size number of bytes available in buf
+ * @param buf where to copy the reply
+ * @return number of bytes written to buf
  */
-static void
-read_response (void *cls,
-              const struct GNUNET_SCHEDULER_TaskContext *tc)
+static size_t
+transmit_reply_to_mesh (void *cls,
+                       size_t size,
+                       void *buf)
 {
-  struct GNUNET_NETWORK_Handle *dnsout = cls;
-  struct sockaddr_in addr4;
-  struct sockaddr_in6 addr6;
-  struct sockaddr *addr;
-  struct GNUNET_TUN_DnsHeader *dns;
+  struct TunnelState *ts = cls;
+  size_t off;
+  size_t ret;
+  char *cbuf = buf;
+  struct GNUNET_MessageHeader hdr;
+  struct GNUNET_TUN_DnsHeader dns;
+
+  ts->th = NULL;
+  GNUNET_assert (ts->reply != NULL);
+  if (size == 0)
+    return 0;
+  ret = sizeof (struct GNUNET_MessageHeader) + ts->reply_length; 
+  GNUNET_assert (ret <= size);
+  hdr.size = htons (ret);
+  hdr.type = htons (GNUNET_MESSAGE_TYPE_VPN_DNS_FROM_INTERNET);
+  memcpy (&dns, ts->reply, sizeof (dns));
+  dns.id = ts->original_id;
+  off = 0;
+  memcpy (&cbuf[off], &hdr, sizeof (hdr));
+  off += sizeof (hdr);
+  memcpy (&cbuf[off], &dns, sizeof (dns));
+  off += sizeof (dns);
+  memcpy (&cbuf[off], &ts->reply[sizeof (dns)], ts->reply_length - sizeof (dns));
+  off += ts->reply_length - sizeof (dns);
+  GNUNET_free (ts->reply);
+  ts->reply = NULL;
+  ts->reply_length = 0;  
+  GNUNET_assert (ret == off);
+  return ret;
+}
+
+
+/**
+ * Actually do the reading of a DNS packet from our UDP socket and see
+ * if we have a valid, matching, pending request.
+ *
+ * @param dnsout socket to read from
+ * @return GNUNET_OK on success, GNUNET_NO on drop, GNUNET_SYSERR on IO-errors (closed socket)
+ */
+static int
+do_dns_read (struct GNUNET_NETWORK_Handle *dnsout)
+{
+  struct sockaddr_storage addr;
   socklen_t addrlen;
+  struct GNUNET_TUN_DnsHeader *dns;
   struct RequestRecord *rr;
+  struct TunnelState *ts;
   ssize_t r;
   int len;
 
-  if (dnsout == dnsout4)
-  {
-    addrlen = sizeof (struct sockaddr_in);
-    addr = (struct sockaddr* ) &addr4;
-    read4_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, 
-                                               dnsout,
-                                               &read_response, 
-                                               dnsout);
-  }
-  else
-  {
-    addrlen = sizeof (struct sockaddr_in6);
-    addr = (struct sockaddr* ) &addr6;
-    read6_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, 
-                                               dnsout,
-                                               &read_response, 
-                                               dnsout);
-  }
-  if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
-    return;
-
 #ifndef MINGW
   if (0 != ioctl (GNUNET_NETWORK_get_fd (dnsout), FIONREAD, &len))
   {
     /* conservative choice: */
-    len = 65536;
+    len = UINT16_MAX;
   }
 #else
   /* port the code above? */
-  len = 65536;
+  len = UINT16_MAX;
 #endif
 
   {
     unsigned char buf[len];
 
-    memset (addr, 0, addrlen);  
+    addrlen = sizeof (addr);
+    memset (&addr, 0, sizeof (addr));  
     r = GNUNET_NETWORK_socket_recvfrom (dnsout, 
                                        buf, sizeof (buf),
-                                       addr, &addrlen);
+                                       (struct sockaddr*) &addr, &addrlen);
     if (-1 == r)
     {
       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "recvfrom");
-      return;
+      GNUNET_NETWORK_socket_close (dnsout);
+      return GNUNET_SYSERR;
     }
     if (sizeof (struct GNUNET_TUN_DnsHeader) > r)
     {
       GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 
                  _("Received DNS response that is too small (%u bytes)"),
                  r);
-      return;
+      return GNUNET_NO;
     }
     dns = (struct GNUNET_TUN_DnsHeader *) buf;
+    /* Handle case that this is a reply to a request from a MESH DNS tunnel */
+    ts = tunnels[dns->id];
+    if ( (NULL == ts) ||
+        (ts->dnsout != dnsout) ||
+        (addrlen != ts->addrlen) ||
+        (0 != memcmp (&ts->addr,
+                      &addr,
+                      addrlen)) ||      
+        (0 == GNUNET_TIME_absolute_get_remaining (ts->timeout).rel_value) )
+      ts = NULL; /* DNS responder address missmatch */
+    if (NULL != ts)
+    {
+      tunnels[dns->id] = NULL;
+      GNUNET_free_non_null (ts->reply);
+      ts->reply = GNUNET_malloc (r);
+      ts->reply_length = r;
+      memcpy (ts->reply, dns, r);
+      if (ts->th != NULL)
+       GNUNET_MESH_notify_transmit_ready_cancel (ts->th);
+      ts->th = GNUNET_MESH_notify_transmit_ready (ts->tunnel,
+                                                 GNUNET_NO, 0,
+                                                 GNUNET_TIME_UNIT_FOREVER_REL,
+                                                 NULL,
+                                                 sizeof (struct GNUNET_MessageHeader) + r,
+                                                 &transmit_reply_to_mesh,
+                                                 ts);
+    }
+    /* Handle case that this is a reply to a local request (intercepted from TUN interface) */
     rr = &requests[dns->id];
-    if (rr->phase != RP_INTERNET_DNS) 
+    if ( (rr->phase != RP_INTERNET_DNS) ||
+        (rr->dnsout != dnsout) ||
+        (0 != memcmp (&rr->dst_addr,
+                      &addr,
+                      addrlen)) ||
+        (0 == GNUNET_TIME_absolute_get_remaining (rr->timeout).rel_value) )
     {
-      /* unexpected / bogus reply */
-      GNUNET_STATISTICS_update (stats,
-                               gettext_noop ("# External DNS response discarded (no matching request)"),
-                               1, GNUNET_NO);
-      return; 
+      if (NULL == ts)
+      {
+       /* unexpected / bogus reply */
+       GNUNET_STATISTICS_update (stats,
+                                 gettext_noop ("# External DNS response discarded (no matching request)"),
+                                 1, GNUNET_NO);
+      }
+      return GNUNET_NO; 
     }
     GNUNET_free_non_null (rr->payload);
-    rr->payload = GNUNET_malloc (len);
-    memcpy (rr->payload, buf, len);
-    rr->payload_length = len;
+    rr->payload = GNUNET_malloc (r);
+    memcpy (rr->payload, buf, r);
+    rr->payload_length = r;
     next_phase (rr);
   }  
-}
-
-
-/**
- * Open source port for sending DNS request on IPv4.
- *
- * @return GNUNET_OK on success
- */ 
-static int
-open_port4 ()
-{
-  struct sockaddr_in addr;
-  socklen_t addrlen;
-
-  dnsout4 = GNUNET_NETWORK_socket_create (AF_INET, SOCK_DGRAM, 0);
-  if (dnsout4 == NULL)
-    return GNUNET_SYSERR;
-
-  memset (&addr, 0, sizeof (struct sockaddr_in));
-  addr.sin_family = AF_INET;
-  int err = GNUNET_NETWORK_socket_bind (dnsout4,
-                                        (struct sockaddr *) &addr,
-                                        sizeof (struct sockaddr_in));
-
-  if (err != GNUNET_OK)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 
-               _("Could not bind to any port: %s\n"),
-               STRERROR (errno));
-    GNUNET_NETWORK_socket_close (dnsout4);
-    dnsout4 = NULL;
-    return GNUNET_SYSERR;
-  }
-
-  /* Read the port we bound to */
-  addrlen = sizeof (struct sockaddr_in);
-  if (0 != getsockname (GNUNET_NETWORK_get_fd (dnsout4), 
-                       (struct sockaddr *) &addr,
-                       &addrlen))
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 
-               _("Could not determine port I got: %s\n"),
-               STRERROR (errno));
-    GNUNET_NETWORK_socket_close (dnsout4);
-    dnsout4 = NULL;
-    return GNUNET_SYSERR;
-  }
-  dnsoutport = htons (addr.sin_port);
-
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO, 
-             _("GNUnet DNS will exit on source port %u\n"),
-             (unsigned int) dnsoutport);
-  read4_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, 
-                                             dnsout4,
-                                             &read_response, dnsout4);
   return GNUNET_OK;
 }
 
 
 /**
- * Open source port for sending DNS request on IPv6.  Should be 
- * called AFTER open_port4.
+ * Read a DNS response from the (unhindered) UDP-Socket
  *
- * @return GNUNET_OK on success
- */ 
-static int
-open_port6 ()
+ * @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)
 {
-  struct sockaddr_in6 addr;
-  socklen_t addrlen;
+  struct RequestSocket *rs = cls;
+  struct GNUNET_NETWORK_FDSet *rset;
 
-  dnsout6 = GNUNET_NETWORK_socket_create (AF_INET6, SOCK_DGRAM, 0);
-  if (dnsout6 == NULL)
+  rs->read_task = GNUNET_SCHEDULER_NO_TASK;
+  if (0 == (tc->reason & GNUNET_SCHEDULER_REASON_READ_READY))
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 
-               _("Could not create IPv6 socket: %s\n"),
-               STRERROR (errno));
-    return GNUNET_SYSERR;
-  }
-  memset (&addr, 0, sizeof (struct sockaddr_in6));
-  addr.sin6_family = AF_INET6;
-  addr.sin6_port = htons (dnsoutport);
-  int err = GNUNET_NETWORK_socket_bind (dnsout6,
-                                        (struct sockaddr *) &addr,
-                                        sizeof (struct sockaddr_in6));
-
-  if (err != GNUNET_OK)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-               _("Could not bind to port %u: %s\n"),
-               (unsigned int) dnsoutport,
-               STRERROR (errno));
-    GNUNET_NETWORK_socket_close (dnsout6);
-    dnsout6 = NULL;
-    return GNUNET_SYSERR;
-  }
-  if (0 == dnsoutport)
-  {
-    addrlen = sizeof (struct sockaddr_in6);
-    if (0 != getsockname (GNUNET_NETWORK_get_fd (dnsout6), 
-                         (struct sockaddr *) &addr,
-                         &addrlen))
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 
-                 _("Could not determine port I got: %s\n"),
-                 STRERROR (errno));
-      GNUNET_NETWORK_socket_close (dnsout6);
-      dnsout6 = NULL;
-      return GNUNET_SYSERR;
-    }
+    /* timeout or shutdown */
+    cleanup_rs (rs);
+    return;
   }
-  dnsoutport = htons (addr.sin6_port);
-  read6_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
-                                             dnsout6,
-                                             &read_response, dnsout6);
-  return GNUNET_YES;
+  /* read and process ready sockets */
+  if ((NULL != rs->dnsout4) &&
+      (GNUNET_NETWORK_fdset_isset (tc->read_ready, rs->dnsout4)) &&
+      (GNUNET_SYSERR == do_dns_read (rs->dnsout4)))
+    rs->dnsout4 = NULL;
+  if ((NULL != rs->dnsout6) &&
+      (GNUNET_NETWORK_fdset_isset (tc->read_ready, rs->dnsout6)) &&
+      (GNUNET_SYSERR == do_dns_read (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);
+  if (NULL != rs->dnsout6)
+    GNUNET_NETWORK_fdset_set (rset, rs->dnsout6);
+  rs->read_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
+                                              GNUNET_SCHEDULER_NO_TASK,
+                                              GNUNET_TIME_absolute_get_remaining (rs->timeout),
+                                              rset,
+                                              NULL,
+                                              &read_response, rs);
+  GNUNET_NETWORK_fdset_destroy (rset);
 }
 
 
@@ -1109,39 +1310,46 @@ process_helper_messages (void *cls GNUNET_UNUSED, void *client,
 
   /* setup new request */
   rr->phase = RP_INIT;
-  if (ip4->version == 4)
+  switch (ntohs (tun->proto))
   {
-    srca4 = (struct sockaddr_in*) &rr->src_addr;
-    dsta4 = (struct sockaddr_in*) &rr->dst_addr;
-    memset (srca4, 0, sizeof (struct sockaddr_in));
-    memset (dsta4, 0, sizeof (struct sockaddr_in));
-    srca4->sin_family = AF_INET;
-    dsta4->sin_family = AF_INET;
-    srca4->sin_addr = ip4->source_address;
-    dsta4->sin_addr = ip4->destination_address;
-    srca4->sin_port = udp->spt;
-    dsta4->sin_port = udp->dpt;
+  case ETH_P_IPV4:
+    {
+      srca4 = (struct sockaddr_in*) &rr->src_addr;
+      dsta4 = (struct sockaddr_in*) &rr->dst_addr;
+      memset (srca4, 0, sizeof (struct sockaddr_in));
+      memset (dsta4, 0, sizeof (struct sockaddr_in));
+      srca4->sin_family = AF_INET;
+      dsta4->sin_family = AF_INET;
+      srca4->sin_addr = ip4->source_address;
+      dsta4->sin_addr = ip4->destination_address;
+      srca4->sin_port = udp->source_port;
+      dsta4->sin_port = udp->destination_port;
 #if HAVE_SOCKADDR_IN_SIN_LEN
-    srca4->sin_len = sizeof (sizeof (struct sockaddr_in));
-    dsta4->sin_len = sizeof (sizeof (struct sockaddr_in));
+      srca4->sin_len = sizeof (struct sockaddr_in);
+      dsta4->sin_len = sizeof (struct sockaddr_in);
 #endif
-  }
-  else /* ipv6 */
-  {
-    srca6 = (struct sockaddr_in6*) &rr->src_addr;
-    dsta6 = (struct sockaddr_in6*) &rr->dst_addr;
-    memset (srca6, 0, sizeof (struct sockaddr_in6));
-    memset (dsta6, 0, sizeof (struct sockaddr_in6));
-    srca6->sin6_family = AF_INET6;
-    dsta6->sin6_family = AF_INET6;
-    srca6->sin6_addr = ip6->source_address;
-    dsta6->sin6_addr = ip6->destination_address;
-    srca6->sin6_port = udp->spt;
-    dsta6->sin6_port = udp->dpt;
+    }
+    break;
+  case ETH_P_IPV6:
+    {
+      srca6 = (struct sockaddr_in6*) &rr->src_addr;
+      dsta6 = (struct sockaddr_in6*) &rr->dst_addr;
+      memset (srca6, 0, sizeof (struct sockaddr_in6));
+      memset (dsta6, 0, sizeof (struct sockaddr_in6));
+      srca6->sin6_family = AF_INET6;
+      dsta6->sin6_family = AF_INET6;
+      srca6->sin6_addr = ip6->source_address;
+      dsta6->sin6_addr = ip6->destination_address;
+      srca6->sin6_port = udp->source_port;
+      dsta6->sin6_port = udp->destination_port;
 #if HAVE_SOCKADDR_IN_SIN_LEN
-    srca6->sin6_len = sizeof (sizeof (struct sockaddr_in6));
-    dsta6->sin6_len = sizeof (sizeof (struct sockaddr_in6));
+      srca6->sin6_len = sizeof (struct sockaddr_in6);
+      dsta6->sin6_len = sizeof (struct sockaddr_in6);
 #endif
+    }
+  break;
+  default:
+    GNUNET_assert (0);
   }
   rr->payload = GNUNET_malloc (msize);
   rr->payload_length = msize;
@@ -1157,6 +1365,150 @@ process_helper_messages (void *cls GNUNET_UNUSED, void *client,
 }
 
 
+/**
+ * Process a request via mesh to perform a DNS query.
+ *
+ * @param cls closure, NULL
+ * @param tunnel connection to the other end
+ * @param tunnel_ctx pointer to our 'struct TunnelState *'
+ * @param sender who sent the message
+ * @param message the actual message
+ * @param atsi performance data for the connection
+ * @return GNUNET_OK to keep the connection open,
+ *         GNUNET_SYSERR to close it (signal serious error)
+ */
+static int
+receive_dns_request (void *cls GNUNET_UNUSED, struct GNUNET_MESH_Tunnel *tunnel,
+                     void **tunnel_ctx,
+                     const struct GNUNET_PeerIdentity *sender GNUNET_UNUSED,
+                     const struct GNUNET_MessageHeader *message,
+                     const struct GNUNET_ATS_Information *atsi GNUNET_UNUSED)
+{
+  struct TunnelState *ts = *tunnel_ctx;
+  const struct GNUNET_TUN_DnsHeader *dns;
+  size_t mlen = ntohs (message->size);
+  size_t dlen = mlen - sizeof (struct GNUNET_MessageHeader);
+  char buf[dlen];
+  struct GNUNET_TUN_DnsHeader *dout;
+  struct sockaddr_in v4;
+  struct sockaddr_in6 v6;
+  struct sockaddr *so;
+  socklen_t salen;
+  
+  if (dlen < sizeof (struct GNUNET_TUN_DnsHeader))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  dns = (const struct GNUNET_TUN_DnsHeader *) &message[1];
+  ts->original_id = dns->id;
+  if (tunnels[ts->my_id] == ts)
+    tunnels[ts->my_id] = NULL;
+  ts->my_id = (uint16_t) GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 
+                                                  UINT16_MAX + 1);
+  tunnels[ts->my_id] = ts;
+  memcpy (buf, dns, dlen);
+  dout = (struct GNUNET_TUN_DnsHeader*) buf;
+  dout->id = ts->my_id;
+  memset (&v4, 0, sizeof (v4));
+  memset (&v6, 0, sizeof (v6));
+  if (1 == inet_pton (AF_INET, dns_exit, &v4.sin_addr))
+  {
+    salen = sizeof (v4);
+    v4.sin_family = AF_INET;
+    v4.sin_port = htons (53);
+#if HAVE_SOCKADDR_IN_SIN_LEN
+    v4.sin_len = (u_char) salen;
+#endif
+    so = (struct sockaddr *) &v4;
+    ts->dnsout = get_request_socket (AF_INET);
+  }
+  else if (1 == inet_pton (AF_INET6, dns_exit, &v6.sin6_addr))
+  {
+    salen = sizeof (v6);
+    v6.sin6_family = AF_INET6;
+    v6.sin6_port = htons (53);
+#if HAVE_SOCKADDR_IN_SIN_LEN
+    v6.sin6_len = (u_char) salen;
+#endif
+    so = (struct sockaddr *) &v6;
+    ts->dnsout = get_request_socket (AF_INET6);
+  }  
+  else
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (NULL == ts->dnsout)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+               _("Configured DNS exit `%s' is not working / valid.\n"),
+               dns_exit);
+    return GNUNET_SYSERR;
+  }
+  memcpy (&ts->addr,
+         so,
+         salen);
+  ts->addrlen = salen;
+  GNUNET_NETWORK_socket_sendto (ts->dnsout,
+                               buf, dlen, so, salen); 
+  ts->timeout = GNUNET_TIME_relative_to_absolute (REQUEST_TIMEOUT);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Callback from GNUNET_MESH for new tunnels.
+ *
+ * @param cls closure
+ * @param tunnel new handle to the tunnel
+ * @param initiator peer that started the tunnel
+ * @param ats performance information for the tunnel
+ * @return initial tunnel context for the tunnel
+ */
+static void *
+accept_dns_tunnel (void *cls GNUNET_UNUSED, struct GNUNET_MESH_Tunnel *tunnel,
+                  const struct GNUNET_PeerIdentity *initiator GNUNET_UNUSED,
+                  const struct GNUNET_ATS_Information *ats GNUNET_UNUSED)
+{
+  struct TunnelState *ts = GNUNET_malloc (sizeof (struct TunnelState));
+
+  GNUNET_STATISTICS_update (stats,
+                           gettext_noop ("# Inbound MESH tunnels created"),
+                           1, GNUNET_NO);
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Received inbound tunnel from `%s'\n",
+             GNUNET_i2s (initiator));
+  ts->tunnel = tunnel;
+  return ts;
+}
+
+
+/**
+ * Function called by mesh whenever an inbound tunnel is destroyed.
+ * Should clean up any associated state.
+ *
+ * @param cls closure (set from GNUNET_MESH_connect)
+ * @param tunnel connection to the other end (henceforth invalid)
+ * @param tunnel_ctx place where local state associated
+ *                   with the tunnel is stored
+ */
+static void
+destroy_dns_tunnel (void *cls GNUNET_UNUSED, 
+                   const struct GNUNET_MESH_Tunnel *tunnel,
+                   void *tunnel_ctx)
+{
+  struct TunnelState *ts = tunnel_ctx;
+
+  if (tunnels[ts->my_id] == ts)
+    tunnels[ts->my_id] = NULL;
+  if (NULL != ts->th)
+    GNUNET_MESH_notify_transmit_ready_cancel (ts->th);
+  GNUNET_free_non_null (ts->reply);
+  GNUNET_free (ts);
+}
+
+
 /**
  * @param cls closure
  * @param server the initialized server
@@ -1173,29 +1525,32 @@ run (void *cls, struct GNUNET_SERVER_Handle *server,
     {&handle_client_response, NULL, GNUNET_MESSAGE_TYPE_DNS_CLIENT_RESPONSE, 0},
     {NULL, NULL, 0, 0}
   };
-  char port_s[6];
   char *ifc_name;
   char *ipv4addr;
   char *ipv4mask;
   char *ipv6addr;
   char *ipv6prefix;
+  struct in_addr dns_exit4;
+  struct in6_addr dns_exit6;
 
   cfg = cfg_;
   stats = GNUNET_STATISTICS_create ("dns", cfg);
   nc = GNUNET_SERVER_notification_context_create (server, 1);
   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &cleanup_task,
                                 cls);
-  if (GNUNET_YES ==
-      GNUNET_CONFIGURATION_get_value_yesno (cfg_, "dns", "PROVIDE_EXIT"))
+  if ( (GNUNET_YES ==
+       GNUNET_CONFIGURATION_get_value_yesno (cfg_, "dns", "PROVIDE_EXIT")) &&
+       ( (GNUNET_OK !=
+         GNUNET_CONFIGURATION_get_value_string (cfg, "dns", 
+                                                "DNS_EXIT",
+                                                &dns_exit)) ||
+        ( (1 != inet_pton (AF_INET, dns_exit, &dns_exit4)) &&
+          (1 != inet_pton (AF_INET6, dns_exit, &dns_exit6)) ) ) )
   {
-    if ( (GNUNET_OK != open_port4 ()) &&
-        (GNUNET_OK != open_port6 ()) )
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                 _("Failed to open any port to provide DNS exit\n"));
-      GNUNET_SCHEDULER_shutdown ();
-      return;
-    }
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+               _("Configured to provide DNS exit, but no valid DNS server configured!\n"));
+    GNUNET_free_non_null (dns_exit);
+    dns_exit = NULL;
   }
 
   helper_argv[0] = GNUNET_strdup ("gnunet-dns");
@@ -1249,12 +1604,25 @@ run (void *cls, struct GNUNET_SERVER_Handle *server,
     return;
   }
   helper_argv[5] = ipv4mask;
-  GNUNET_snprintf (port_s, 
-                  sizeof (port_s), 
-                  "%u", 
-                  (unsigned int) dnsoutport);
-  helper_argv[6] = GNUNET_strdup (port_s);
-  helper_argv[7] = NULL;
+  helper_argv[6] = NULL;
+
+  if (NULL != dns_exit)
+  {
+    static struct GNUNET_MESH_MessageHandler mesh_handlers[] = {
+      {&receive_dns_request, GNUNET_MESSAGE_TYPE_VPN_DNS_TO_INTERNET, 0},
+      {NULL, 0, 0}
+    };
+    static GNUNET_MESH_ApplicationType mesh_types[] = {
+      GNUNET_APPLICATION_TYPE_INTERNET_RESOLVER,
+      GNUNET_APPLICATION_TYPE_END
+    };
+    mesh = GNUNET_MESH_connect (cfg,
+                               1, NULL,
+                               &accept_dns_tunnel, 
+                               &destroy_dns_tunnel,
+                               mesh_handlers,
+                               mesh_types);
+  }
   hijacker = GNUNET_HELPER_start ("gnunet-helper-dns",
                                  helper_argv,
                                  &process_helper_messages,
@@ -1280,4 +1648,4 @@ main (int argc, char *const *argv)
 }
 
 
-/* end of gnunet-service-dns_new.c */
+/* end of gnunet-service-dns.c */