tolerate additional IPv4 address now available for gnunet.org
[oweals/gnunet.git] / src / util / gnunet-service-resolver.c
index e9397e0854ab805ca7a03e5b24270ec8b1517884..336e35f94476c079e0113a7fe9d312dfa79d7a5d 100644 (file)
@@ -14,6 +14,8 @@
 
      You should have received a copy of the GNU Affero General Public License
      along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+     SPDX-License-Identifier: AGPL3.0-or-later
 */
 
 /**
@@ -57,6 +59,7 @@ struct RecordListEntry
    * Cached data.
    */
   struct GNUNET_DNSPARSER_Record *record;
+
 };
 
 
@@ -143,7 +146,7 @@ struct ActiveLookup
    * Unique request ID of a client if a query for this hostname/record_type
    * is currently pending, undefined otherwise.
    */
-  uint16_t request_id;
+  uint32_t client_request_id;
 
   /**
    * Unique DNS request ID of a client if a query for this hostname/record_type
@@ -164,6 +167,16 @@ static struct ResolveCache *cache_head;
  */
 static struct ResolveCache *cache_tail;
 
+/**
+ * Head of the linked list of DNS lookup results from /etc/hosts.
+ */
+static struct ResolveCache *hosts_head;
+
+/**
+ * Tail of the linked list of DNS lookup results from /etc/hosts.
+ */
+static struct ResolveCache *hosts_tail;
+
 /**
  * Start of the linked list of active DNS lookups.
  */
@@ -179,11 +192,17 @@ static struct ActiveLookup *lookup_tail;
  */
 static struct GNUNET_DNSSTUB_Context *dnsstub_ctx;
 
+/**
+ * My domain, to be appended to the hostname to get a FQDN.
+ */
+static char *my_domain;
+
 /**
  * How many entries do we have in #cache_head DLL?
  */
 static unsigned int cache_size;
 
+
 /**
  * Remove @a entry from cache.
  *
@@ -212,6 +231,34 @@ free_cache_entry (struct ResolveCache *rc)
 }
 
 
+/**
+ * Remove @a entry from cache.
+ *
+ * @param rc entry to free
+ */
+static void
+free_hosts_entry (struct ResolveCache *rc)
+{
+  struct RecordListEntry *pos;
+
+  while (NULL != (pos = rc->records_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (rc->records_head,
+                                rc->records_tail,
+                                pos);
+    GNUNET_DNSPARSER_free_record (pos->record);
+    GNUNET_free (pos->record);
+    GNUNET_free (pos);
+  }
+  GNUNET_free_non_null (rc->hostname);
+  GNUNET_CONTAINER_DLL_remove (hosts_head,
+                               hosts_tail,
+                               rc);
+  cache_size--;
+  GNUNET_free (rc);
+}
+
+
 /**
  * Release resources associated with @a al
  *
@@ -261,6 +308,28 @@ extract_dns_server (const char* line,
 }
 
 
+/**
+ * Find out if the configuration file line contains a string
+ * starting with "search ", and if so, return a copy of
+ * the machine's search domain.
+ *
+ * @param line line to parse
+ * @param line_len number of characters in @a line
+ * @return NULL if no nameserver is configured in this @a line
+ */
+static char *
+extract_search_domain (const char* line,
+                      size_t line_len)
+{
+  if (0 == strncmp (line,
+                    "search ",
+                    strlen ("search ")))
+    return GNUNET_strndup (line + strlen ("search "),
+                           line_len - strlen ("search "));
+  return NULL;
+}
+
+
 /**
  * Reads the list of nameservers from /etc/resolve.conf
  *
@@ -271,8 +340,9 @@ static int
 lookup_dns_servers (char ***server_addrs)
 {
   struct GNUNET_DISK_FileHandle *fh;
-  char buf[2048];
-  ssize_t bytes_read;
+  struct GNUNET_DISK_MapHandle *mh;
+  off_t bytes_read;
+  const char *buf;
   size_t read_offset;
   unsigned int num_dns_servers;
 
@@ -286,13 +356,32 @@ lookup_dns_servers (char ***server_addrs)
                "DNS resolution will not be possible.\n");
     return -1;
   }
-  bytes_read = GNUNET_DISK_file_read (fh,
-                                     buf,
-                                     sizeof (buf));
+  if (GNUNET_OK !=
+      GNUNET_DISK_file_handle_size (fh,
+                                   &bytes_read))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+               "Could not determine size of /etc/resolv.conf. "
+               "DNS resolution will not be possible.\n");
+    GNUNET_DISK_file_close (fh);
+    return -1;
+  }
+  if ((size_t) bytes_read > SIZE_MAX)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+               "/etc/resolv.conf file too large to mmap. "
+               "DNS resolution will not be possible.\n");
+    GNUNET_DISK_file_close (fh);
+    return -1;
+  }
+  buf = GNUNET_DISK_file_map (fh,
+                             &mh,
+                             GNUNET_DISK_MAP_TYPE_READ,
+                             (size_t) bytes_read);
   *server_addrs = NULL;
   read_offset = 0;
   num_dns_servers = 0;
-  while (read_offset < bytes_read)
+  while (read_offset < (size_t) bytes_read)
   {
     const char *newline;
     size_t line_len;
@@ -306,11 +395,19 @@ lookup_dns_servers (char ***server_addrs)
     dns_server = extract_dns_server (buf + read_offset,
                                      line_len);
     if (NULL != dns_server)
+    {
       GNUNET_array_append (*server_addrs,
                           num_dns_servers,
                           dns_server);
+    }
+    else if (NULL == my_domain)
+    {
+      my_domain = extract_search_domain (buf + read_offset,
+                                        line_len);
+    }
     read_offset += line_len + 1;
   }
+  GNUNET_DISK_file_unmap (mh);
   GNUNET_DISK_file_close (fh);
   return (int) num_dns_servers;
 }
@@ -392,7 +489,7 @@ make_reverse_hostname (const void *ip,
  *
  * @param record information to transmit
  * @param record_type requested record type from client
- * @param request_id to which request are we responding
+ * @param client_request_id to which request are we responding
  * @param client where to send @a record
  * @return #GNUNET_YES if we sent a reply,
  *         #GNUNET_NO if the record type is not understood or
@@ -401,7 +498,7 @@ make_reverse_hostname (const void *ip,
 static int
 send_reply (struct GNUNET_DNSPARSER_Record *record,
             uint16_t record_type,
-           uint16_t request_id,
+           uint32_t client_request_id,
            struct GNUNET_SERVICE_Client *client)
 {
   struct GNUNET_RESOLVER_ResponseMessage *msg;
@@ -446,7 +543,7 @@ send_reply (struct GNUNET_DNSPARSER_Record *record,
   env = GNUNET_MQ_msg_extra (msg,
                             payload_len,
                             GNUNET_MESSAGE_TYPE_RESOLVER_RESPONSE);
-  msg->id = request_id;
+  msg->client_id = client_request_id;
   GNUNET_memcpy (&msg[1],
                 payload,
                 payload_len);
@@ -458,13 +555,13 @@ send_reply (struct GNUNET_DNSPARSER_Record *record,
 
 /**
  * Send message to @a client that we transmitted all
- * responses for @a request_id
+ * responses for @a client_request_id
  *
- * @param request_id to which request are we responding
+ * @param client_request_id to which request are we responding
  * @param client where to send @a record
  */
 static void
-send_end_msg (uint16_t request_id,
+send_end_msg (uint32_t client_request_id,
              struct GNUNET_SERVICE_Client *client)
 {
   struct GNUNET_RESOLVER_ResponseMessage *msg;
@@ -474,7 +571,7 @@ send_end_msg (uint16_t request_id,
              "Sending END message\n");
   env = GNUNET_MQ_msg (msg,
                       GNUNET_MESSAGE_TYPE_RESOLVER_RESPONSE);
-  msg->id = request_id;
+  msg->client_id = client_request_id;
   GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
                  env);
 }
@@ -499,9 +596,14 @@ remove_expired (struct ResolveCache *rc)
   {
     n = pos->next;
     if (now.abs_value_us > pos->record->expiration_time.abs_value_us)
+    {
       GNUNET_CONTAINER_DLL_remove (rc->records_head,
                                    rc->records_tail,
                                    pos);
+      GNUNET_DNSPARSER_free_record (pos->record);
+      GNUNET_free (pos->record);
+      GNUNET_free (pos);
+    }
   }
   if (NULL == rc->records_head)
   {
@@ -518,13 +620,13 @@ remove_expired (struct ResolveCache *rc)
  *
  * @param hostname DNS name to resolve
  * @param record_type desired record type
- * @param request_id client's request ID
+ * @param client_request_id client's request ID
  * @param client who should get the result?
  */
 static void
 process_get (const char *hostname,
             uint16_t record_type,
-            uint16_t request_id,
+            uint32_t client_request_id,
             struct GNUNET_SERVICE_Client *client);
 
 
@@ -536,28 +638,40 @@ process_get (const char *hostname,
  *
  * @param hostname what hostname was to be resolved
  * @param record_type what type of record was requested
- * @param request_id unique identification of the client's request
+ * @param client_request_id unique identification of the client's request
  * @param client handle to the client making the request (for sending the reply)
  */
 static int
 try_cache (const char *hostname,
            uint16_t record_type,
-          uint16_t request_id,
+          uint32_t client_request_id,
           struct GNUNET_SERVICE_Client *client)
 {
   struct ResolveCache *pos;
   struct ResolveCache *next;
   int found;
+  int in_hosts;
 
-  next = cache_head;
-  for (pos = next; NULL != pos; pos = next)
-  {
-    next = pos->next;
-    if (GNUNET_YES == remove_expired (pos))
-      continue;
+  in_hosts = GNUNET_NO;
+  for (pos = hosts_head; NULL != pos; pos = pos->next)
     if (0 == strcmp (pos->hostname,
                      hostname))
+    {
+      in_hosts = GNUNET_YES;
       break;
+    }
+  if (NULL == pos)
+  {
+    next = cache_head;
+    for (pos = next; NULL != pos; pos = next)
+    {
+      next = pos->next;
+      if (GNUNET_YES == remove_expired (pos))
+       continue;
+      if (0 == strcmp (pos->hostname,
+                      hostname))
+       break;
+    }
   }
   if (NULL == pos)
   {
@@ -566,7 +680,8 @@ try_cache (const char *hostname,
                 hostname);
     return GNUNET_NO;
   }
-  if (cache_head != pos)
+  if ( (GNUNET_NO == in_hosts) &&
+       (cache_head != pos) )
   {
     /* move result to head to achieve LRU for cache eviction */
     GNUNET_CONTAINER_DLL_remove (cache_head,
@@ -595,18 +710,18 @@ try_cache (const char *hostname,
 
       process_get (hostname,
                    record_type,
-                   request_id,
+                   client_request_id,
                    client);
       return GNUNET_YES; /* counts as a cache "hit" */
     }
     found |= send_reply (rle->record,
                          record_type,
-                         request_id,
+                         client_request_id,
                          client);
   }
   if (GNUNET_NO == found)
     return GNUNET_NO; /* had records, but none matched! */
-  send_end_msg (request_id,
+  send_end_msg (client_request_id,
                 client);
   return GNUNET_YES;
 }
@@ -658,6 +773,41 @@ pack (const char *hostname,
   return GNUNET_OK;
 }
 
+static void
+cache_answers(const char* name,
+              struct GNUNET_DNSPARSER_Record *records,
+              unsigned int num_records)
+{
+  struct ResolveCache *rc;
+  struct GNUNET_DNSPARSER_Record *record;
+  struct RecordListEntry *rle;
+
+  for (unsigned int i = 0; i != num_records; i++)
+  {
+    record = &records[i];
+
+    for (rc = cache_head; NULL != rc; rc = rc->next)
+      if (0 == strcasecmp (rc->hostname,
+                           name))
+        break;
+    if (NULL == rc)
+    {
+      rc = GNUNET_new (struct ResolveCache);
+      rc->hostname = GNUNET_strdup (name);
+      GNUNET_CONTAINER_DLL_insert (cache_head,
+                                   cache_tail,
+                                   rc);
+      cache_size++;
+    }
+    /* TODO: ought to check first if we have this exact record
+       already in the cache! */
+    rle = GNUNET_new (struct RecordListEntry);
+    rle->record = GNUNET_DNSPARSER_duplicate_record (record);
+    GNUNET_CONTAINER_DLL_insert (rc->records_head,
+                                 rc->records_tail,
+                                 rle);
+  }
+}
 
 /**
  * We got a result from DNS. Add it to the cache and
@@ -669,39 +819,47 @@ pack (const char *hostname,
  */
 static void
 handle_resolve_result (void *cls,
-                      const struct GNUNET_TUN_DnsHeader *dns,
+                       const struct GNUNET_TUN_DnsHeader *dns,
                        size_t dns_len)
 {
   struct ActiveLookup *al = cls;
   struct GNUNET_DNSPARSER_Packet *parsed;
-  struct ResolveCache *rc;
 
   parsed = GNUNET_DNSPARSER_parse ((const char *)dns,
-                                  dns_len);
+                                   dns_len);
   if (NULL == parsed)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-               "Failed to parse DNS reply (hostname %s, request ID %u)\n",
+                "Failed to parse DNS reply (hostname %s, request ID %u)\n",
                 al->hostname,
-               al->dns_id);
+                al->dns_id);
     return;
   }
   if (al->dns_id != ntohs (parsed->id))
   {
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-               "Request ID in DNS reply does not match\n");
+                "Request ID in DNS reply does not match\n");
     GNUNET_DNSPARSER_free_packet (parsed);
     return;
   }
-  if (0 == parsed->num_answers)
+  if (0 == parsed->num_answers + parsed->num_authority_records + parsed->num_additional_records)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-               "DNS reply (hostname %s, request ID %u) contains no answers\n",
+                "DNS reply (hostname %s, request ID %u) contains no answers\n",
                 al->hostname,
-               al->request_id);
+                (unsigned int) al->client_request_id);
+    /* resume by trying again from cache */
+    if (GNUNET_NO ==
+        try_cache (al->hostname,
+                   al->record_type,
+                   al->client_request_id,
+                   al->client))
+      /* cache failed, tell client we could not get an answer */
+    {
+      send_end_msg (al->client_request_id,
+                    al->client);
+    }
     GNUNET_DNSPARSER_free_packet (parsed);
-    send_end_msg (al->request_id,
-                  al->client);
     free_active_lookup (al);
     return;
   }
@@ -712,34 +870,15 @@ handle_resolve_result (void *cls,
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Got reply for hostname %s and request ID %u\n",
               al->hostname,
-              al->request_id);
+              (unsigned int) al->client_request_id);
   /* add to cache */
-  for (unsigned int i = 0; i != parsed->num_answers; i++)
-  {
-    struct GNUNET_DNSPARSER_Record *record = &parsed->answers[i];
-    struct RecordListEntry *rle;
+  cache_answers(al->hostname,
+                parsed->answers, parsed->num_answers);
+  cache_answers(al->hostname,
+                parsed->authority_records, parsed->num_authority_records);
+  cache_answers(al->hostname,
+                parsed->additional_records, parsed->num_additional_records);
 
-    for (rc = cache_head; NULL != rc; rc = rc->next)
-      if (0 == strcasecmp (rc->hostname,
-                           record->name))
-        break;
-    if (NULL == rc)
-    {
-      rc = GNUNET_new (struct ResolveCache);
-      rc->hostname = GNUNET_strdup (record->name);
-      GNUNET_CONTAINER_DLL_insert (cache_head,
-                                   cache_tail,
-                                   rc);
-      cache_size++;
-    }
-    /* TODO: ought to check first if we have this exact record
-       already in the cache! */
-    rle = GNUNET_new (struct RecordListEntry);
-    rle->record = GNUNET_DNSPARSER_duplicate_record (record);
-    GNUNET_CONTAINER_DLL_insert (rc->records_head,
-                                 rc->records_tail,
-                                 rle);
-  }
   /* see if we need to do the 2nd request for AAAA records */
   if ( (GNUNET_DNSPARSER_TYPE_ALL == al->record_type) &&
        (GNUNET_NO == al->did_aaaa) )
@@ -766,6 +905,8 @@ handle_resolve_result (void *cls,
                                 packet_size,
                                 &handle_resolve_result,
                                 al);
+      GNUNET_free (packet_buf);
+      GNUNET_DNSPARSER_free_packet (parsed);
       return;
     }
   }
@@ -774,11 +915,13 @@ handle_resolve_result (void *cls,
   if (GNUNET_NO ==
       try_cache (al->hostname,
                  al->record_type,
-                 al->request_id,
+                 al->client_request_id,
                  al->client))
     /* cache failed, tell client we could not get an answer */
-    send_end_msg (al->request_id,
+  {
+    send_end_msg (al->client_request_id,
                   al->client);
+  }
   free_active_lookup (al);
   GNUNET_DNSPARSER_free_packet (parsed);
 }
@@ -797,8 +940,8 @@ handle_resolve_timeout (void *cls)
 
   al->timeout_task = NULL;
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-             "DNS lookup timeout!\n");
-  send_end_msg (al->request_id,
+              "DNS lookup timeout!\n");
+  send_end_msg (al->client_request_id,
                 al->client);
   free_active_lookup (al);
 }
@@ -810,15 +953,15 @@ handle_resolve_timeout (void *cls)
  *
  * @param hostname DNS name to resolve
  * @param record_type record type to locate
- * @param request_id client request ID
+ * @param client_request_id client request ID
  * @param client handle to the client
  * @return #GNUNET_OK if the DNS query is now pending
  */
 static int
 resolve_and_cache (const char* hostname,
-                  uint16_t record_type,
-                  uint16_t request_id,
-                  struct GNUNET_SERVICE_Client *client)
+                   uint16_t record_type,
+                   uint32_t client_request_id,
+                   struct GNUNET_SERVICE_Client *client)
 {
   char *packet_buf;
   size_t packet_size;
@@ -827,7 +970,8 @@ resolve_and_cache (const char* hostname,
   uint16_t type;
 
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-             "resolve_and_cache\n");
+              "resolve_and_cache `%s'\n",
+              hostname);
   dns_id = (uint16_t) GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
                                                 UINT16_MAX);
 
@@ -843,7 +987,7 @@ resolve_and_cache (const char* hostname,
             &packet_size))
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-               "Failed to pack query for hostname `%s'\n",
+                "Failed to pack query for hostname `%s'\n",
                 hostname);
     return GNUNET_SYSERR;
   }
@@ -851,7 +995,7 @@ resolve_and_cache (const char* hostname,
   al = GNUNET_new (struct ActiveLookup);
   al->hostname = GNUNET_strdup (hostname);
   al->record_type = record_type;
-  al->request_id = request_id;
+  al->client_request_id = client_request_id;
   al->dns_id = dns_id;
   al->client = client;
   al->timeout_task = GNUNET_SCHEDULER_add_delayed (DNS_TIMEOUT,
@@ -859,51 +1003,83 @@ resolve_and_cache (const char* hostname,
                                                    al);
   al->resolve_handle =
     GNUNET_DNSSTUB_resolve (dnsstub_ctx,
-                           packet_buf,
-                           packet_size,
-                           &handle_resolve_result,
-                           al);
+                            packet_buf,
+                            packet_size,
+                            &handle_resolve_result,
+                            al);
   GNUNET_free (packet_buf);
   GNUNET_CONTAINER_DLL_insert (lookup_head,
-                              lookup_tail,
-                              al);
+                               lookup_tail,
+                               al);
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-             "Resolving %s, request_id = %u, dns_id = %u\n",
-             hostname,
-              (unsigned int) request_id,
-             (unsigned int) dns_id);
+              "Resolving %s, client_request_id = %u, dns_id = %u\n",
+              hostname,
+              (unsigned int) client_request_id,
+              (unsigned int) dns_id);
   return GNUNET_OK;
 }
 
 
 /**
- * Process DNS request for @a hostname with request ID @a request_id
+ * Process DNS request for @a hostname with request ID @a client_request_id
  * from @a client demanding records of type @a record_type.
  *
  * @param hostname DNS name to resolve
  * @param record_type desired record type
- * @param request_id client's request ID
+ * @param client_request_id client's request ID
  * @param client who should get the result?
  */
 static void
 process_get (const char *hostname,
-            uint16_t record_type,
-            uint16_t request_id,
-            struct GNUNET_SERVICE_Client *client)
+             uint16_t record_type,
+             uint32_t client_request_id,
+             struct GNUNET_SERVICE_Client *client)
 {
-  if (GNUNET_NO ==
+  char fqdn[255];
+
+  if (GNUNET_NO !=
       try_cache (hostname,
                  record_type,
-                 request_id,
+                 client_request_id,
+                 client))
+    return;
+  if (  (NULL != my_domain) &&
+        (NULL == strchr (hostname,
+                         (unsigned char) '.')) &&
+        (strlen (hostname) + strlen (my_domain) <= 253) )
+  {
+    GNUNET_snprintf (fqdn,
+                     sizeof (fqdn),
+                     "%s.%s",
+                     hostname,
+                     my_domain);
+  }
+  else if (strlen (hostname) < 255)
+  {
+    GNUNET_snprintf (fqdn,
+                     sizeof (fqdn),
+                     "%s",
+                     hostname);
+  }
+  else
+  {
+    GNUNET_break (0);
+    GNUNET_SERVICE_client_drop (client);
+    return;
+  }
+  if (GNUNET_NO ==
+      try_cache (fqdn,
+                 record_type,
+                 client_request_id,
                  client))
   {
     if (GNUNET_OK !=
-        resolve_and_cache (hostname,
+        resolve_and_cache (fqdn,
                            record_type,
-                           request_id,
+                           client_request_id,
                            client))
     {
-      send_end_msg (request_id,
+      send_end_msg (client_request_id,
                     client);
     }
   }
@@ -919,7 +1095,7 @@ process_get (const char *hostname,
  */
 static int
 check_get (void *cls,
-          const struct GNUNET_RESOLVER_GetMessage *get)
+           const struct GNUNET_RESOLVER_GetMessage *get)
 {
   uint16_t size;
   int direction;
@@ -930,37 +1106,29 @@ check_get (void *cls,
   direction = ntohl (get->direction);
   if (GNUNET_NO == direction)
   {
-    /* IP from hostname */
-    const char *hostname;
-
-    hostname = (const char *) &get[1];
-    if (hostname[size - 1] != '\0')
-    {
-      GNUNET_break (0);
-      return GNUNET_SYSERR;
-    }
+    GNUNET_MQ_check_zero_termination (get);
     return GNUNET_OK;
   }
   af = ntohl (get->af);
   switch (af)
   {
-  case AF_INET:
-    if (size != sizeof (struct in_addr))
-    {
-      GNUNET_break (0);
-      return GNUNET_SYSERR;
-    }
-    break;
-  case AF_INET6:
-    if (size != sizeof (struct in6_addr))
-    {
+    case AF_INET:
+      if (size != sizeof (struct in_addr))
+      {
+        GNUNET_break (0);
+        return GNUNET_SYSERR;
+      }
+      break;
+    case AF_INET6:
+      if (size != sizeof (struct in6_addr))
+      {
+        GNUNET_break (0);
+        return GNUNET_SYSERR;
+      }
+      break;
+    default:
       GNUNET_break (0);
       return GNUNET_SYSERR;
-    }
-    break;
-  default:
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
   }
   return GNUNET_OK;
 }
@@ -974,54 +1142,58 @@ check_get (void *cls,
  */
 static void
 handle_get (void *cls,
-           const struct GNUNET_RESOLVER_GetMessage *msg)
+            const struct GNUNET_RESOLVER_GetMessage *msg)
 {
   struct GNUNET_SERVICE_Client *client = cls;
   int direction;
   int af;
-  uint16_t request_id;
+  uint32_t client_request_id;
   char *hostname;
 
   direction = ntohl (msg->direction);
   af = ntohl (msg->af);
-  request_id = ntohs (msg->id);
+  client_request_id = msg->client_id;
+  GNUNET_SERVICE_client_continue (client);
   if (GNUNET_NO == direction)
   {
     /* IP from hostname */
     hostname = GNUNET_strdup ((const char *) &msg[1]);
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+               "Client asks to resolve `%s'\n",
+               hostname);
     switch (af)
     {
       case AF_UNSPEC:
-      {
-       process_get (hostname,
-                     GNUNET_DNSPARSER_TYPE_ALL,
-                     request_id,
-                     client);
-       break;
-      }
+        {
+          process_get (hostname,
+                       GNUNET_DNSPARSER_TYPE_ALL,
+                       client_request_id,
+                       client);
+          break;
+        }
       case AF_INET:
-      {
-       process_get (hostname,
-                     GNUNET_DNSPARSER_TYPE_A,
-                     request_id,
-                     client);
-        break;
-      }
+        {
+          process_get (hostname,
+                       GNUNET_DNSPARSER_TYPE_A,
+                       client_request_id,
+                       client);
+          break;
+        }
       case AF_INET6:
-      {
-       process_get (hostname,
-                     GNUNET_DNSPARSER_TYPE_AAAA,
-                     request_id,
-                     client);
-        break;
-      }
+        {
+          process_get (hostname,
+                       GNUNET_DNSPARSER_TYPE_AAAA,
+                       client_request_id,
+                       client);
+          break;
+        }
       default:
-      {
-        GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                 "got invalid af: %d\n",
-                 af);
-        GNUNET_assert (0);
-      }
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                      "got invalid af: %d\n",
+                      af);
+          GNUNET_assert (0);
+        }
     }
   }
   else
@@ -1031,11 +1203,10 @@ handle_get (void *cls,
                                       af);
     process_get (hostname,
                  GNUNET_DNSPARSER_TYPE_PTR,
-                 request_id,
+                 client_request_id,
                  client);
   }
   GNUNET_free_non_null (hostname);
-  GNUNET_SERVICE_client_continue (client);
 }
 
 
@@ -1053,7 +1224,174 @@ shutdown_task (void *cls)
     free_active_lookup (lookup_head);
   while (NULL != cache_head)
     free_cache_entry (cache_head);
+  while (NULL != hosts_head)
+    free_hosts_entry (hosts_head);
   GNUNET_DNSSTUB_stop (dnsstub_ctx);
+  GNUNET_free_non_null (my_domain);
+}
+
+
+/**
+ * Add information about a host from /etc/hosts
+ * to our cache.
+ *
+ * @param hostname the name of the host
+ * @param rec_type DNS record type to use
+ * @param data payload
+ * @param data_size number of bytes in @a data
+ */
+static void
+add_host (const char *hostname,
+          uint16_t rec_type,
+          const void *data,
+          size_t data_size)
+{
+  struct ResolveCache *rc;
+  struct RecordListEntry *rle;
+  struct GNUNET_DNSPARSER_Record *rec;
+
+  rec = GNUNET_malloc (sizeof (struct GNUNET_DNSPARSER_Record));
+  rec->expiration_time = GNUNET_TIME_UNIT_FOREVER_ABS;
+  rec->type = rec_type;
+  rec->dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET;
+  rec->name = GNUNET_strdup (hostname);
+  rec->data.raw.data = GNUNET_memdup (data,
+                                      data_size);
+  rec->data.raw.data_len = data_size;
+  rle = GNUNET_new (struct RecordListEntry);
+  rle->record = rec;
+  rc = GNUNET_new (struct ResolveCache);
+  rc->hostname = GNUNET_strdup (hostname);
+  GNUNET_CONTAINER_DLL_insert (rc->records_head,
+                               rc->records_tail,
+                               rle);
+  GNUNET_CONTAINER_DLL_insert (hosts_head,
+                               hosts_tail,
+                               rc);
+}
+
+
+/**
+ * Extract host information from a line in /etc/hosts
+ *
+ * @param line the line to parse
+ * @param line_len number of bytes in @a line
+ */
+static void
+extract_hosts (const char *line,
+               size_t line_len)
+{
+  const char *c;
+  struct in_addr v4;
+  struct in6_addr v6;
+  char *tbuf;
+  char *tok;
+
+  /* ignore everything after '#' */
+  c = memrchr (line,
+               (unsigned char) '#',
+               line_len);
+  if (NULL != c)
+    line_len = c - line;
+  /* ignore leading whitespace */
+  while ( (0 < line_len) &&
+          isspace ((unsigned char) *line) )
+  {
+    line++;
+    line_len--;
+  }
+  tbuf = GNUNET_strndup (line,
+                         line_len);
+  tok = strtok (tbuf, " \t");
+  if (NULL == tok)
+  {
+    GNUNET_free (tbuf);
+    return;
+  }
+  if (1 == inet_pton (AF_INET,
+                      tok,
+                      &v4))
+  {
+    while (NULL != (tok = strtok (NULL, " \t")))
+      add_host (tok,
+                GNUNET_DNSPARSER_TYPE_A,
+                &v4,
+                sizeof (struct in_addr));
+  }
+  else if (1 == inet_pton (AF_INET6,
+                           tok,
+                           &v6))
+  {
+    while (NULL != (tok = strtok (NULL, " \t")))
+      add_host (tok,
+                GNUNET_DNSPARSER_TYPE_AAAA,
+                &v6,
+                sizeof (struct in6_addr));
+  }
+  GNUNET_free (tbuf);
+}
+
+
+/**
+ * Reads the list of hosts from /etc/hosts.
+ */
+static void
+load_etc_hosts (void)
+{
+  struct GNUNET_DISK_FileHandle *fh;
+  struct GNUNET_DISK_MapHandle *mh;
+  off_t bytes_read;
+  const char *buf;
+  size_t read_offset;
+
+  fh = GNUNET_DISK_file_open ("/etc/hosts",
+                              GNUNET_DISK_OPEN_READ,
+                              GNUNET_DISK_PERM_NONE);
+  if (NULL == fh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Failed to open /etc/hosts");
+    return;
+  }
+  if (GNUNET_OK !=
+      GNUNET_DISK_file_handle_size (fh,
+                                    &bytes_read))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not determin size of /etc/hosts. "
+                "DNS resolution will not be possible.\n");
+    GNUNET_DISK_file_close (fh);
+    return;
+  }
+  if ((size_t) bytes_read > SIZE_MAX)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "/etc/hosts file too large to mmap. "
+                "DNS resolution will not be possible.\n");
+    GNUNET_DISK_file_close (fh);
+    return;
+  }
+  buf = GNUNET_DISK_file_map (fh,
+                              &mh,
+                              GNUNET_DISK_MAP_TYPE_READ,
+                              (size_t) bytes_read);
+  read_offset = 0;
+  while (read_offset < (size_t) bytes_read)
+  {
+    const char *newline;
+    size_t line_len;
+
+    newline = strchr (buf + read_offset,
+                      '\n');
+    if (NULL == newline)
+      break;
+    line_len = newline - buf - read_offset;
+    extract_hosts (buf + read_offset,
+                   line_len);
+    read_offset += line_len + 1;
+  }
+  GNUNET_DISK_file_unmap (mh);
+  GNUNET_DISK_file_close (fh);
 }
 
 
@@ -1066,30 +1404,33 @@ shutdown_task (void *cls)
  */
 static void
 init_cb (void *cls,
-        const struct GNUNET_CONFIGURATION_Handle *cfg,
-        struct GNUNET_SERVICE_Handle *sh)
+         const struct GNUNET_CONFIGURATION_Handle *cfg,
+         struct GNUNET_SERVICE_Handle *sh)
 {
   char **dns_servers;
   int num_dns_servers;
 
   (void) cfg;
   (void) sh;
+  load_etc_hosts ();
   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
-                                cls);
+                                 cls);
   dnsstub_ctx = GNUNET_DNSSTUB_start (128);
+  dns_servers = NULL;
   num_dns_servers = lookup_dns_servers (&dns_servers);
   if (0 >= num_dns_servers)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-               _("No DNS server available. DNS resolution will not be possible.\n"));
+                _("No DNS server available. DNS resolution will not be possible.\n"));
+    return;
   }
   for (int i = 0; i < num_dns_servers; i++)
   {
     int result = GNUNET_DNSSTUB_add_dns_ip (dnsstub_ctx, dns_servers[i]);
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-               "Adding DNS server '%s': %s\n",
-               dns_servers[i],
-               GNUNET_OK == result ? "success" : "failure");
+                "Adding DNS server '%s': %s\n",
+                dns_servers[i],
+                GNUNET_OK == result ? "success" : "failure");
     GNUNET_free (dns_servers[i]);
   }
   GNUNET_free_non_null (dns_servers);
@@ -1106,8 +1447,8 @@ init_cb (void *cls,
  */
 static void *
 connect_cb (void *cls,
-           struct GNUNET_SERVICE_Client *c,
-           struct GNUNET_MQ_Handle *mq)
+            struct GNUNET_SERVICE_Client *c,
+            struct GNUNET_MQ_Handle *mq)
 {
   (void) cls;
   (void) mq;
@@ -1125,8 +1466,8 @@ connect_cb (void *cls,
  */
 static void
 disconnect_cb (void *cls,
-              struct GNUNET_SERVICE_Client *c,
-              void *internal_cls)
+               struct GNUNET_SERVICE_Client *c,
+               void *internal_cls)
 {
   struct ActiveLookup *n;
   (void) cls;
@@ -1155,9 +1496,9 @@ GNUNET_SERVICE_MAIN
  &disconnect_cb,
  NULL,
  GNUNET_MQ_hd_var_size (get,
-                       GNUNET_MESSAGE_TYPE_RESOLVER_REQUEST,
-                       struct GNUNET_RESOLVER_GetMessage,
-                       NULL),
+                        GNUNET_MESSAGE_TYPE_RESOLVER_REQUEST,
+                        struct GNUNET_RESOLVER_GetMessage,
+                        NULL),
  GNUNET_MQ_handler_end ());