-remove trailing whitespace
[oweals/gnunet.git] / src / dns / gnunet-service-dns.c
index 5dc6641e74d7c3758137230ee42c32baae01b064..53c19d01cdacbb2e7c638ade8c7f3314c973224b 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_dnsparser_lib.h"
+#include "gnunet_dnsstub_lib.h"
 #include "gnunet_statistics_service.h"
 #include "gnunet_tun_lib.h"
 
+/**
+ * Port number for DNS
+ */
+#define DNS_PORT 53
+
+
+/**
+ * Generic logging shorthand
+ */
+#define LOG(kind, ...)                          \
+  GNUNET_log_from (kind, "dns", __VA_ARGS__);
+
 
 /**
  * Phases each request goes through.
@@ -59,7 +88,7 @@ enum RequestPhase
    * Global Internet query is now pending.
    */
   RP_INTERNET_DNS,
-  
+
   /**
    * Client (or global DNS request) has resulted in a response.
    * Forward to all POST-RESOLUTION clients.  If client list is empty,
@@ -82,22 +111,22 @@ enum RequestPhase
 
 /**
  * Entry we keep for each client.
- */ 
+ */
 struct ClientRecord
 {
   /**
    * Kept in doubly-linked list.
-   */ 
+   */
   struct ClientRecord *next;
 
   /**
    * Kept in doubly-linked list.
-   */ 
+   */
   struct ClientRecord *prev;
 
   /**
    * Handle to the client.
-   */ 
+   */
   struct GNUNET_SERVER_Client *client;
 
   /**
@@ -110,7 +139,7 @@ struct ClientRecord
 
 /**
  * Entry we keep for each active request.
- */ 
+ */
 struct RequestRecord
 {
 
@@ -126,6 +155,12 @@ struct RequestRecord
    */
   char *payload;
 
+  /**
+   * Socket we are using to transmit this request (must match if we receive
+   * a response).
+   */
+  struct GNUNET_DNSSTUB_RequestSocket *rs;
+
   /**
    * Source address of the original request (for sending response).
    */
@@ -145,7 +180,7 @@ struct RequestRecord
 
   /**
    * Number of bytes in payload.
-   */ 
+   */
   size_t payload_length;
 
   /**
@@ -162,32 +197,9 @@ 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.
+ * Global return value from 'main'.
  */
-static struct GNUNET_NETWORK_Handle *dnsout4;
-
-/**
- * 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;
-
-/**
- * Task for reading from dnsout4.
- */
-static GNUNET_SCHEDULER_TaskIdentifier read4_task;
-
-/**
- * Task for reading from dnsout6.
- */
-static GNUNET_SCHEDULER_TaskIdentifier read6_task;
-
-/**
- * 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;
+static int global_ret;
 
 /**
  * The configuration to use
@@ -207,7 +219,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.
@@ -234,6 +246,11 @@ static struct RequestRecord requests[UINT16_MAX + 1];
  */
 static uint64_t request_id_gen;
 
+/**
+ * Handle to the DNS Stub resolver.
+ */
+static struct GNUNET_DNSSTUB_Context *dnsstub;
+
 
 /**
  * We're done processing a DNS request, free associated memory.
@@ -264,39 +281,24 @@ cleanup_task (void *cls GNUNET_UNUSED,
 {
   unsigned int i;
 
-  GNUNET_HELPER_stop (hijacker);
+  GNUNET_HELPER_stop (hijacker, GNUNET_NO);
   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)
-  {
-    GNUNET_SCHEDULER_cancel (read4_task);
-    read4_task = GNUNET_SCHEDULER_NO_TASK;
-  }
-  if (NULL != dnsout6)
-  {
-    GNUNET_NETWORK_socket_close (dnsout6);
-    dnsout6 = NULL;
-  }
-  if (GNUNET_SCHEDULER_NO_TASK != read6_task)
-  {
-    GNUNET_SCHEDULER_cancel (read6_task);
-    read6_task = GNUNET_SCHEDULER_NO_TASK;
-  }
-  for (i=0;i<65536;i++)
+  for (i=0;i<=UINT16_MAX;i++)
     cleanup_rr (&requests[i]);
   GNUNET_SERVER_notification_context_destroy (nc);
   nc = NULL;
   if (stats != NULL)
   {
-    GNUNET_STATISTICS_destroy (stats, GNUNET_YES);
+    GNUNET_STATISTICS_destroy (stats, GNUNET_NO);
     stats = NULL;
   }
+  if (NULL != dnsstub)
+  {
+    GNUNET_DNSSTUB_stop (dnsstub);
+    dnsstub = NULL;
+  }
 }
 
 
@@ -310,19 +312,25 @@ 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,
-                    0); 
+                    0);
   if (RP_RESPONSE_MONITOR != rr->phase)
   {
     /* no response, drop */
+    LOG (GNUNET_ERROR_TYPE_DEBUG,
+        "Got no response for request %llu, dropping\n",
+        (unsigned long long) rr->request_id);
     cleanup_rr (rr);
     return;
   }
-  
+
+  LOG (GNUNET_ERROR_TYPE_DEBUG,
+       "Transmitting response for request %llu\n",
+       (unsigned long long) rr->request_id);
   /* send response via hijacker */
   reply_len = sizeof (struct GNUNET_MessageHeader);
   reply_len += sizeof (struct GNUNET_TUN_Layer2PacketHeader);
@@ -337,7 +345,7 @@ request_done (struct RequestRecord *rr)
   default:
     GNUNET_break (0);
     cleanup_rr (rr);
-    return;   
+    return;
   }
   reply_len += sizeof (struct GNUNET_TUN_UdpHeader);
   reply_len += rr->payload_length;
@@ -346,12 +354,13 @@ request_done (struct RequestRecord *rr)
     /* response too big, drop */
     GNUNET_break (0); /* how can this be? */
     cleanup_rr(rr);
-    return;    
+    return;
   }
   {
-    char buf[reply_len];
+    char buf[reply_len] GNUNET_ALIGN;
     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;
@@ -365,7 +374,7 @@ request_done (struct RequestRecord *rr)
 
       tun.flags = htons (0);
       if (rr->src_addr.ss_family == AF_INET)
-       tun.proto = htons (ETH_P_IPV4); 
+       tun.proto = htons (ETH_P_IPV4);
       else
        tun.proto = htons (ETH_P_IPV6);
       memcpy (&buf[off], &tun, sizeof (struct GNUNET_TUN_Layer2PacketHeader));
@@ -373,78 +382,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 +424,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);
@@ -476,10 +448,10 @@ request_done (struct RequestRecord *rr)
     }
     /* final checks & sending */
     GNUNET_assert (off == reply_len);
-    GNUNET_HELPER_send (hijacker,
-                       hdr,
-                       GNUNET_YES,
-                       NULL, NULL);
+    (void) GNUNET_HELPER_send (hijacker,
+                              hdr,
+                              GNUNET_YES,
+                              NULL, NULL);
     GNUNET_STATISTICS_update (stats,
                              gettext_noop ("# DNS requests answered via TUN interface"),
                              1, GNUNET_NO);
@@ -500,7 +472,7 @@ static void
 send_request_to_client (struct RequestRecord *rr,
                        struct GNUNET_SERVER_Client *client)
 {
-  char buf[sizeof (struct GNUNET_DNS_Request) + rr->payload_length];
+  char buf[sizeof (struct GNUNET_DNS_Request) + rr->payload_length] GNUNET_ALIGN;
   struct GNUNET_DNS_Request *req;
 
   if (sizeof (buf) >= GNUNET_SERVER_MAX_MESSAGE_SIZE)
@@ -509,19 +481,39 @@ send_request_to_client (struct RequestRecord *rr,
     cleanup_rr (rr);
     return;
   }
+  LOG (GNUNET_ERROR_TYPE_DEBUG,
+       "Sending information about request %llu to local client\n",
+       (unsigned long long) rr->request_id);
   req = (struct GNUNET_DNS_Request*) buf;
   req->header.type = htons (GNUNET_MESSAGE_TYPE_DNS_CLIENT_REQUEST);
   req->header.size = htons (sizeof (buf));
   req->reserved = htonl (0);
   req->request_id = rr->request_id;
   memcpy (&req[1], rr->payload, rr->payload_length);
-  GNUNET_SERVER_notification_context_unicast (nc, 
+  GNUNET_SERVER_notification_context_unicast (nc,
                                              client,
                                              &req->header,
                                              GNUNET_NO);
 }
 
 
+
+/**
+ * Callback called from DNSSTUB resolver when a resolution
+ * succeeded.
+ *
+ * @param cls NULL
+ * @param rs the socket that received the response
+ * @param dns the response itself
+ * @param r number of bytes in dns
+ */
+static void
+process_dns_result (void *cls,
+                   struct GNUNET_DNSSTUB_RequestSocket *rs,
+                   const struct GNUNET_TUN_DnsHeader *dns,
+                   size_t r);
+
+
 /**
  * A client has completed its processing for this
  * request.  Move on.
@@ -534,7 +526,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)
@@ -550,13 +541,17 @@ next_phase (struct RequestRecord *rr)
       nz = (int) j;
       break;
     }
-  }  
-  if (-1 != nz) 
+  }
+  if (-1 != nz)
   {
     send_request_to_client (rr, rr->client_wait_list[nz]->client);
     return;
   }
   /* done with current phase, advance! */
+  LOG (GNUNET_ERROR_TYPE_DEBUG,
+       "Request %llu now in phase %d\n",
+       rr->request_id,
+       rr->phase);
   switch (rr->phase)
   {
   case RP_INIT:
@@ -582,35 +577,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->rs = GNUNET_DNSSTUB_resolve (dnsstub,
+                                    (struct sockaddr*) &rr->dst_addr,
+                                    salen,
+                                    rr->payload,
+                                    rr->payload_length,
+                                    &process_dns_result,
+                                    NULL);
+    if (NULL == rr->rs)
     {
       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,
-                                 rr->payload,
-                                 rr->payload_length,
-                                 (struct sockaddr*) &rr->dst_addr,
-                                 salen);
     return;
   case RP_INTERNET_DNS:
     rr->phase = RP_MODIFY;
@@ -653,7 +647,7 @@ next_phase (struct RequestRecord *rr)
  *
  * @param cls unused
  * @param client handle of client that disconnected
- */ 
+ */
 static void
 client_disconnect (void *cls, struct GNUNET_SERVER_Client *client)
 {
@@ -680,7 +674,7 @@ client_disconnect (void *cls, struct GNUNET_SERVER_Client *client)
          if (rr->client_wait_list[j] == cr)
          {
            rr->client_wait_list[j] = NULL;
-           next_phase (rr); 
+           next_phase (rr);
          }
        }
       }
@@ -692,208 +686,45 @@ client_disconnect (void *cls, struct GNUNET_SERVER_Client *client)
 
 
 /**
- * Read a DNS response from the (unhindered) UDP-Socket
+ * Callback called from DNSSTUB resolver when a resolution
+ * succeeded.
  *
- * @param cls socket to read from
- * @param tc scheduler context (must be shutdown or read ready)
+ * @param cls NULL
+ * @param rs the socket that received the response
+ * @param dns the response itself
+ * @param r number of bytes in dns
  */
 static void
-read_response (void *cls,
-              const struct GNUNET_SCHEDULER_TaskContext *tc)
+process_dns_result (void *cls,
+                   struct GNUNET_DNSSTUB_RequestSocket *rs,
+                   const struct GNUNET_TUN_DnsHeader *dns,
+                   size_t r)
 {
-  struct GNUNET_NETWORK_Handle *dnsout = cls;
-  struct sockaddr_in addr4;
-  struct sockaddr_in6 addr6;
-  struct sockaddr *addr;
-  struct GNUNET_TUN_DnsHeader *dns;
-  socklen_t addrlen;
   struct RequestRecord *rr;
-  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
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Processing DNS result from stub resolver\n");
+  GNUNET_assert (NULL == cls);
+  rr = &requests[dns->id];
+  if ( (rr->phase != RP_INTERNET_DNS) ||
+       (rr->rs != rs) )
   {
-    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))
+    /* unexpected / bogus reply */
+    GNUNET_STATISTICS_update (stats,
+                             gettext_noop ("# External DNS response discarded (no matching request)"),
+                             1, GNUNET_NO);
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+               "Received DNS reply that does not match any pending request.  Dropping.\n");
     return;
-
-#ifndef MINGW
-  if (0 != ioctl (GNUNET_NETWORK_get_fd (dnsout), FIONREAD, &len))
-  {
-    /* conservative choice: */
-    len = 65536;
-  }
-#else
-  /* port the code above? */
-  len = 65536;
-#endif
-
-  {
-    unsigned char buf[len];
-
-    memset (addr, 0, addrlen);  
-    r = GNUNET_NETWORK_socket_recvfrom (dnsout, 
-                                       buf, sizeof (buf),
-                                       addr, &addrlen);
-    if (-1 == r)
-    {
-      GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "recvfrom");
-      return;
-    }
-    if (sizeof (struct GNUNET_TUN_DnsHeader) > r)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 
-                 _("Received DNS response that is too small (%u bytes)"),
-                 r);
-      return;
-    }
-    dns = (struct GNUNET_TUN_DnsHeader *) buf;
-    rr = &requests[dns->id];
-    if (rr->phase != RP_INTERNET_DNS) 
-    {
-      /* unexpected / bogus reply */
-      GNUNET_STATISTICS_update (stats,
-                               gettext_noop ("# External DNS response discarded (no matching request)"),
-                               1, GNUNET_NO);
-      return; 
-    }
-    GNUNET_free_non_null (rr->payload);
-    rr->payload = GNUNET_malloc (len);
-    memcpy (rr->payload, buf, len);
-    rr->payload_length = len;
-    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.
- *
- * @return GNUNET_OK on success
- */ 
-static int
-open_port6 ()
-{
-  struct sockaddr_in6 addr;
-  socklen_t addrlen;
-
-  dnsout6 = GNUNET_NETWORK_socket_create (AF_INET6, SOCK_DGRAM, 0);
-  if (dnsout6 == NULL)
-  {
-    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;
-    }
-  }
-  dnsoutport = htons (addr.sin6_port);
-  read6_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
-                                             dnsout6,
-                                             &read_response, dnsout6);
-  return GNUNET_YES;
+  LOG (GNUNET_ERROR_TYPE_DEBUG,
+       "Got a response from the stub resolver for DNS request %llu intercepted locally!\n",
+       (unsigned long long) rr->request_id);
+  GNUNET_free_non_null (rr->payload);
+  rr->payload = GNUNET_malloc (r);
+  memcpy (rr->payload, dns, r);
+  rr->payload_length = r;
+  next_phase (rr);
 }
 
 
@@ -905,7 +736,7 @@ open_port6 ()
  * @param message the init message (unused)
  */
 static void
-handle_client_init (void *cls GNUNET_UNUSED, 
+handle_client_init (void *cls GNUNET_UNUSED,
                    struct GNUNET_SERVER_Client *client,
                    const struct GNUNET_MessageHeader *message)
 {
@@ -914,7 +745,7 @@ handle_client_init (void *cls GNUNET_UNUSED,
 
   cr = GNUNET_malloc (sizeof (struct ClientRecord));
   cr->client = client;
-  cr->flags = (enum GNUNET_DNS_Flags) ntohl (reg->flags);  
+  cr->flags = (enum GNUNET_DNS_Flags) ntohl (reg->flags);
   GNUNET_SERVER_client_keep (client);
   GNUNET_CONTAINER_DLL_insert (clients_head,
                               clients_tail,
@@ -932,7 +763,7 @@ handle_client_init (void *cls GNUNET_UNUSED,
  * @param message the response
  */
 static void
-handle_client_response (void *cls GNUNET_UNUSED, 
+handle_client_response (void *cls GNUNET_UNUSED,
                        struct GNUNET_SERVER_Client *client,
                        const struct GNUNET_MessageHeader *message)
 {
@@ -952,6 +783,9 @@ handle_client_response (void *cls GNUNET_UNUSED,
   resp = (const struct GNUNET_DNS_Response*) message;
   off = (uint16_t) resp->request_id;
   rr = &requests[off];
+  LOG (GNUNET_ERROR_TYPE_DEBUG,
+       "Received DNS response with ID %llu from local client!\n",
+       (unsigned long long) resp->request_id);
   if (rr->request_id != resp->request_id)
   {
     GNUNET_STATISTICS_update (stats,
@@ -982,14 +816,12 @@ handle_client_response (void *cls GNUNET_UNUSED,
       {
        GNUNET_break (0);
        GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
-       next_phase (rr); 
+       next_phase (rr);
        return;
       }
       GNUNET_free_non_null (rr->payload);
-#if DEBUG_DNS
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                 _("Changing DNS reply according to client specifications\n"));
-#endif
+                 "Changing DNS reply according to client specifications\n");
       rr->payload = GNUNET_malloc (msize);
       rr->payload_length = msize;
       memcpy (rr->payload, &resp[1], msize);
@@ -1000,11 +832,21 @@ handle_client_response (void *cls GNUNET_UNUSED,
                           rr->client_wait_list_length,
                           0);
       }
+      /* if query changed to answer, move past DNS resolution phase... */
+      if ( (RP_QUERY == rr->phase) &&
+          (rr->payload_length > sizeof (struct GNUNET_TUN_DnsHeader)) &&
+          ((struct GNUNET_TUN_DnsFlags*)&(((struct GNUNET_TUN_DnsHeader*) rr->payload)->flags))->query_or_response == 1)
+      {
+       rr->phase = RP_INTERNET_DNS;
+       GNUNET_array_grow (rr->client_wait_list,
+                          rr->client_wait_list_length,
+                          0);
+      }
       break;
     }
-    next_phase (rr); 
+    next_phase (rr);
     GNUNET_SERVER_receive_done (client, GNUNET_OK);
-    return;      
+    return;
   }
   /* odd, client was not on our list for the request, that ought
      to be an error */
@@ -1021,7 +863,7 @@ handle_client_response (void *cls GNUNET_UNUSED,
  * @param client identification of the client
  * @param message the actual message, a DNS request we should handle
  */
-static void
+static int
 process_helper_messages (void *cls GNUNET_UNUSED, void *client,
                         const struct GNUNET_MessageHeader *message)
 {
@@ -1037,12 +879,14 @@ process_helper_messages (void *cls GNUNET_UNUSED, void *client,
   struct sockaddr_in *dsta4;
   struct sockaddr_in6 *dsta6;
 
+  LOG (GNUNET_ERROR_TYPE_DEBUG,
+       "Intercepted message via DNS hijacker\n");
   msize = ntohs (message->size);
   if (msize < sizeof (struct GNUNET_MessageHeader) + sizeof (struct GNUNET_TUN_Layer2PacketHeader) + sizeof (struct GNUNET_TUN_IPv4Header))
   {
     /* non-IP packet received on TUN!? */
     GNUNET_break (0);
-    return;
+    return GNUNET_OK;
   }
   msize -= sizeof (struct GNUNET_MessageHeader);
   tun = (const struct GNUNET_TUN_Layer2PacketHeader *) &message[1];
@@ -1051,6 +895,7 @@ process_helper_messages (void *cls GNUNET_UNUSED, void *client,
   {
   case ETH_P_IPV4:
     ip4 = (const struct GNUNET_TUN_IPv4Header *) &tun[1];
+    ip6 = NULL; /* make compiler happy */
     if ( (msize < sizeof (struct GNUNET_TUN_IPv4Header)) ||
         (ip4->version != 4) ||
         (ip4->header_length != sizeof (struct GNUNET_TUN_IPv4Header) / 4) ||
@@ -1060,12 +905,13 @@ process_helper_messages (void *cls GNUNET_UNUSED, void *client,
       /* non-IP/UDP packet received on TUN (or with options) */
       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  _("Received malformed IPv4-UDP packet on TUN interface.\n"));
-      return;
+      return GNUNET_OK;
     }
     udp = (const struct GNUNET_TUN_UdpHeader*) &ip4[1];
     msize -= sizeof (struct GNUNET_TUN_IPv4Header);
     break;
   case ETH_P_IPV6:
+    ip4 = NULL; /* make compiler happy */
     ip6 = (const struct GNUNET_TUN_IPv6Header *) &tun[1];
     if ( (msize < sizeof (struct GNUNET_TUN_IPv6Header)) ||
         (ip6->version != 6) ||
@@ -1075,7 +921,7 @@ process_helper_messages (void *cls GNUNET_UNUSED, void *client,
       /* non-IP/UDP packet received on TUN (or with extensions) */
       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  _("Received malformed IPv6-UDP packet on TUN interface.\n"));
-      return;
+      return GNUNET_OK;
     }
     udp = (const struct GNUNET_TUN_UdpHeader*) &ip6[1];
     msize -= sizeof (struct GNUNET_TUN_IPv6Header);
@@ -1086,15 +932,16 @@ process_helper_messages (void *cls GNUNET_UNUSED, void *client,
                _("Got non-IP packet with %u bytes and protocol %u from TUN\n"),
                (unsigned int) msize,
                ntohs (tun->proto));
-    return;
+    return GNUNET_OK;
   }
-  if (msize <= sizeof (struct GNUNET_TUN_UdpHeader) + sizeof (struct GNUNET_TUN_DnsHeader))
-  {    
+  if ( (msize <= sizeof (struct GNUNET_TUN_UdpHeader) + sizeof (struct GNUNET_TUN_DnsHeader)) ||
+       (DNS_PORT != ntohs (udp->destination_port)) )
+  {
     /* non-DNS packet received on TUN, ignore */
     GNUNET_STATISTICS_update (stats,
                              gettext_noop ("# Non-DNS UDP packet received via TUN interface"),
                              1, GNUNET_NO);
-    return;
+    return GNUNET_OK;
   }
   msize -= sizeof (struct GNUNET_TUN_UdpHeader);
   dns = (const struct GNUNET_TUN_DnsHeader*) &udp[1];
@@ -1109,51 +956,61 @@ 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;
   memcpy (rr->payload, dns, msize);
   rr->request_id = dns->id | (request_id_gen << 16);
   request_id_gen++;
-
+  LOG (GNUNET_ERROR_TYPE_DEBUG,
+       "Creating new DNS request %llu\n",
+       (unsigned long long) rr->request_id);
   GNUNET_STATISTICS_update (stats,
                            gettext_noop ("# DNS requests received via TUN interface"),
                            1, GNUNET_NO);
   /* start request processing state machine */
   next_phase (rr);
+  return GNUNET_OK;
 }
 
 
@@ -1168,36 +1025,52 @@ run (void *cls, struct GNUNET_SERVER_Handle *server,
 {
   static const struct GNUNET_SERVER_MessageHandler handlers[] = {
     /* callback, cls, type, size */
-    {&handle_client_init, NULL, GNUNET_MESSAGE_TYPE_DNS_CLIENT_INIT, 
+    {&handle_client_init, NULL, GNUNET_MESSAGE_TYPE_DNS_CLIENT_INIT,
      sizeof (struct GNUNET_DNS_Register)},
     {&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;
+  char *dns_exit;
+  char *binary;
 
   cfg = cfg_;
+  binary = GNUNET_OS_get_libexec_binary_path ("gnunet-helper-dns");
+  if (GNUNET_YES !=
+      GNUNET_OS_check_helper_binary (binary, GNUNET_YES, NULL)) // TODO: once we have a windows-testcase, add test parameters here
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+               _("`%s' must be installed SUID, refusing to run\n"),
+               binary);
+    global_ret = 1;
+    GNUNET_free (binary);
+    return;
+  }
+  GNUNET_free (binary);
   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"))
+  dns_exit = NULL;
+  if ( ( (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_config_invalid (GNUNET_ERROR_TYPE_ERROR, "dns", "DNS_EXIT",
+                              _("need a valid IPv4 or IPv6 address\n"));
+    GNUNET_free_non_null (dns_exit);
+    dns_exit = NULL;
   }
-
+  dnsstub = GNUNET_DNSSTUB_start (dns_exit);
   helper_argv[0] = GNUNET_strdup ("gnunet-dns");
   if (GNUNET_SYSERR ==
       GNUNET_CONFIGURATION_get_value_string (cfg, "dns", "IFNAME", &ifc_name))
@@ -1249,16 +1122,12 @@ 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;
-  hijacker = GNUNET_HELPER_start ("gnunet-helper-dns",
+  helper_argv[6] = NULL;
+  hijacker = GNUNET_HELPER_start (GNUNET_NO,
+                                 "gnunet-helper-dns",
                                  helper_argv,
                                  &process_helper_messages,
-                                 NULL);
+                                 NULL, NULL);
   GNUNET_SERVER_add_handlers (server, handlers);
   GNUNET_SERVER_disconnect_notify (server, &client_disconnect, NULL);
 }
@@ -1274,10 +1143,29 @@ run (void *cls, struct GNUNET_SERVER_Handle *server,
 int
 main (int argc, char *const *argv)
 {
+  /* make use of SGID capabilities on POSIX */
+  /* FIXME: this might need a port on systems without 'getresgid' */
+#if HAVE_GETRESGID
+  gid_t rgid;
+  gid_t egid;
+  gid_t sgid;
+
+  if (-1 == getresgid (&rgid, &egid, &sgid))
+  {
+    fprintf (stderr,
+            "getresgid failed: %s\n",
+            strerror (errno));
+  }
+  else if (sgid != rgid)
+  {
+    if (-1 ==  setregid (sgid, sgid))
+      fprintf (stderr, "setregid failed: %s\n", strerror (errno));
+  }
+#endif
   return (GNUNET_OK ==
           GNUNET_SERVICE_run (argc, argv, "dns", GNUNET_SERVICE_OPTION_NONE,
-                              &run, NULL)) ? 0 : 1;
+                              &run, NULL)) ? global_ret : 1;
 }
 
 
-/* end of gnunet-service-dns_new.c */
+/* end of gnunet-service-dns.c */