handle case that DNS servers do not return A/AAAA records if we ask for ALL
[oweals/gnunet.git] / src / util / gnunet-service-resolver.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2007-2016 GNUnet e.V.
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      Affero General Public License for more details.
14
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /**
20  * @file util/gnunet-service-resolver.c
21  * @brief code to do DNS resolution
22  * @author Christian Grothoff
23  */
24 #include "platform.h"
25 #include "gnunet_util_lib.h"
26 #include "gnunet_protocols.h"
27 #include "gnunet_statistics_service.h"
28 #include "resolver.h"
29
30
31 /**
32  * How long do we wait for DNS answers?
33  */
34 #define DNS_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30)
35
36 /**
37  * Maximum number of hostnames we cache results for.
38  */
39 #define MAX_CACHE 1024
40
41 /**
42  * Entry in list of cached DNS records for a hostname.
43  */
44 struct RecordListEntry
45 {
46   /**
47    * This is a doubly linked list.
48    */
49   struct RecordListEntry *next;
50
51   /**
52    * This is a doubly linked list.
53    */
54   struct RecordListEntry *prev;
55
56   /**
57    * Cached data.
58    */
59   struct GNUNET_DNSPARSER_Record *record;
60 };
61
62
63 /**
64  * A cached DNS lookup result.
65  */
66 struct ResolveCache
67 {
68   /**
69    * This is a doubly linked list.
70    */
71   struct ResolveCache *next;
72
73   /**
74    * This is a doubly linked list.
75    */
76   struct ResolveCache *prev;
77
78   /**
79    * Which hostname is this cache for?
80    */
81   char *hostname;
82
83   /**
84    * head of a double linked list containing the lookup results
85    */
86   struct RecordListEntry *records_head;
87
88   /**
89    * tail of a double linked list containing the lookup results
90    */
91   struct RecordListEntry *records_tail;
92
93 };
94
95
96 /**
97  * Information about pending lookups.
98  */
99 struct ActiveLookup
100 {
101   /**
102    * Stored in a DLL.
103    */
104   struct ActiveLookup *next;
105
106   /**
107    * Stored in a DLL.
108    */
109   struct ActiveLookup *prev;
110
111   /**
112    * The client that queried the records contained in this cache entry.
113    */
114   struct GNUNET_SERVICE_Client *client;
115
116   /**
117    * handle for cancelling a request
118    */
119   struct GNUNET_DNSSTUB_RequestSocket *resolve_handle;
120
121   /**
122    * handle for the resolution timeout task
123    */
124   struct GNUNET_SCHEDULER_Task *timeout_task;
125
126   /**
127    * Which hostname are we resolving?
128    */
129   char *hostname;
130
131   /**
132    * If @a record_type is #GNUNET_DNSPARSER_TYPE_ALL, did we go again
133    * for the AAAA records yet?
134    */
135   int did_aaaa;
136
137   /**
138    * type of queried DNS record
139    */
140   uint16_t record_type;
141
142   /**
143    * Unique request ID of a client if a query for this hostname/record_type
144    * is currently pending, undefined otherwise.
145    */
146   uint16_t request_id;
147
148   /**
149    * Unique DNS request ID of a client if a query for this hostname/record_type
150    * is currently pending, undefined otherwise.
151    */
152   uint16_t dns_id;
153
154 };
155
156
157 /**
158  * Start of the linked list of cached DNS lookup results.
159  */
160 static struct ResolveCache *cache_head;
161
162 /**
163  * Tail of the linked list of cached DNS lookup results.
164  */
165 static struct ResolveCache *cache_tail;
166
167 /**
168  * Start of the linked list of active DNS lookups.
169  */
170 static struct ActiveLookup *lookup_head;
171
172 /**
173  * Tail of the linked list of active DNS lookups.
174  */
175 static struct ActiveLookup *lookup_tail;
176
177 /**
178  * context of dnsstub library
179  */
180 static struct GNUNET_DNSSTUB_Context *dnsstub_ctx;
181
182 /**
183  * How many entries do we have in #cache_head DLL?
184  */
185 static unsigned int cache_size;
186
187 /**
188  * Remove @a entry from cache.
189  *
190  * @param rc entry to free
191  */
192 static void
193 free_cache_entry (struct ResolveCache *rc)
194 {
195   struct RecordListEntry *pos;
196
197   while (NULL != (pos = rc->records_head))
198   {
199     GNUNET_CONTAINER_DLL_remove (rc->records_head,
200                                  rc->records_tail,
201                                  pos);
202     GNUNET_DNSPARSER_free_record (pos->record);
203     GNUNET_free (pos->record);
204     GNUNET_free (pos);
205   }
206   GNUNET_free_non_null (rc->hostname);
207   GNUNET_CONTAINER_DLL_remove (cache_head,
208                                cache_tail,
209                                rc);
210   cache_size--;
211   GNUNET_free (rc);
212 }
213
214
215 /**
216  * Release resources associated with @a al
217  *
218  * @param al an active lookup
219  */
220 static void
221 free_active_lookup (struct ActiveLookup *al)
222 {
223   GNUNET_CONTAINER_DLL_remove (lookup_head,
224                                lookup_tail,
225                                al);
226   if (NULL != al->resolve_handle)
227   {
228     GNUNET_DNSSTUB_resolve_cancel (al->resolve_handle);
229     al->resolve_handle = NULL;
230   }
231   if (NULL != al->timeout_task)
232   {
233     GNUNET_SCHEDULER_cancel (al->timeout_task);
234     al->timeout_task = NULL;
235   }
236   GNUNET_free_non_null (al->hostname);
237   GNUNET_free (al);
238 }
239
240
241
242 /**
243  * Find out if the configuration file line contains a string
244  * starting with "nameserver ", and if so, return a copy of
245  * the nameserver's IP.
246  *
247  * @param line line to parse
248  * @param line_len number of characters in @a line
249  * @return NULL if no nameserver is configured in this @a line
250  */
251 static char *
252 extract_dns_server (const char* line,
253                     size_t line_len)
254 {
255   if (0 == strncmp (line,
256                     "nameserver ",
257                     strlen ("nameserver ")))
258     return GNUNET_strndup (line + strlen ("nameserver "),
259                            line_len - strlen ("nameserver "));
260   return NULL;
261 }
262
263
264 /**
265  * Reads the list of nameservers from /etc/resolve.conf
266  *
267  * @param server_addrs[out] a list of null-terminated server address strings
268  * @return the number of server addresses in @server_addrs, -1 on error
269  */
270 static int
271 lookup_dns_servers (char ***server_addrs)
272 {
273   struct GNUNET_DISK_FileHandle *fh;
274   char buf[2048];
275   ssize_t bytes_read;
276   size_t read_offset;
277   unsigned int num_dns_servers;
278
279   fh = GNUNET_DISK_file_open ("/etc/resolv.conf",
280                               GNUNET_DISK_OPEN_READ,
281                               GNUNET_DISK_PERM_NONE);
282   if (NULL == fh)
283   {
284     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
285                 "Could not open /etc/resolv.conf. "
286                 "DNS resolution will not be possible.\n");
287     return -1;
288   }
289   bytes_read = GNUNET_DISK_file_read (fh,
290                                       buf,
291                                       sizeof (buf));
292   *server_addrs = NULL;
293   read_offset = 0;
294   num_dns_servers = 0;
295   while (read_offset < bytes_read)
296   {
297     const char *newline;
298     size_t line_len;
299     char *dns_server;
300
301     newline = strchr (buf + read_offset,
302                       '\n');
303     if (NULL == newline)
304       break;
305     line_len = newline - buf - read_offset;
306     dns_server = extract_dns_server (buf + read_offset,
307                                      line_len);
308     if (NULL != dns_server)
309       GNUNET_array_append (*server_addrs,
310                            num_dns_servers,
311                            dns_server);
312     read_offset += line_len + 1;
313   }
314   GNUNET_DISK_file_close (fh);
315   return (int) num_dns_servers;
316 }
317
318
319 /**
320  * Compute name to use for DNS reverse lookups from @a ip.
321  *
322  * @param ip IP address to resolve, in binary format, network byte order
323  * @param af address family of @a ip, AF_INET or AF_INET6
324  */
325 static char *
326 make_reverse_hostname (const void *ip,
327                        int af)
328 {
329   char *buf = GNUNET_new_array (80,
330                                 char);
331   int pos = 0;
332
333   if (AF_INET == af)
334   {
335     struct in_addr *addr = (struct in_addr *)ip;
336     uint32_t ip_int = addr->s_addr;
337
338     for (int i = 3; i >= 0; i--)
339     {
340       int n = GNUNET_snprintf (buf + pos,
341                                80 - pos,
342                                "%u.",
343                                ((uint8_t *)&ip_int)[i]);
344       if (n < 0)
345       {
346         GNUNET_free (buf);
347         return NULL;
348       }
349       pos += n;
350     }
351     pos += GNUNET_snprintf (buf + pos,
352                             80 - pos,
353                             "in-addr.arpa");
354   }
355   else if (AF_INET6 == af)
356   {
357     struct in6_addr *addr = (struct in6_addr *)ip;
358     for (int i = 15; i >= 0; i--)
359     {
360       int n = GNUNET_snprintf (buf + pos,
361                                80 - pos,
362                                "%x.",
363                                addr->s6_addr[i] & 0xf);
364       if (n < 0)
365       {
366         GNUNET_free (buf);
367         return NULL;
368       }
369       pos += n;
370       n = GNUNET_snprintf (buf + pos,
371                            80 - pos,
372                            "%x.",
373                            addr->s6_addr[i] >> 4);
374       if (n < 0)
375       {
376         GNUNET_free (buf);
377         return NULL;
378       }
379       pos += n;
380     }
381     pos += GNUNET_snprintf (buf + pos,
382                             80 - pos,
383                             "ip6.arpa");
384   }
385   buf[pos] = '\0';
386   return buf;
387 }
388
389
390 /**
391  * Send DNS @a record back to our @a client.
392  *
393  * @param record information to transmit
394  * @param record_type requested record type from client
395  * @param request_id to which request are we responding
396  * @param client where to send @a record
397  * @return #GNUNET_YES if we sent a reply,
398  *         #GNUNET_NO if the record type is not understood or
399  *         does not match @a record_type
400  */
401 static int
402 send_reply (struct GNUNET_DNSPARSER_Record *record,
403             uint16_t record_type,
404             uint16_t request_id,
405             struct GNUNET_SERVICE_Client *client)
406 {
407   struct GNUNET_RESOLVER_ResponseMessage *msg;
408   struct GNUNET_MQ_Envelope *env;
409   const void *payload;
410   size_t payload_len;
411
412   switch (record->type)
413   {
414   case GNUNET_DNSPARSER_TYPE_CNAME:
415     if (GNUNET_DNSPARSER_TYPE_CNAME != record_type)
416       return GNUNET_NO;
417     payload = record->data.hostname;
418     payload_len = strlen (record->data.hostname) + 1;
419     break;
420   case GNUNET_DNSPARSER_TYPE_PTR:
421     if (GNUNET_DNSPARSER_TYPE_PTR != record_type)
422       return GNUNET_NO;
423     payload = record->data.hostname;
424     payload_len = strlen (record->data.hostname) + 1;
425     break;
426   case GNUNET_DNSPARSER_TYPE_A:
427     if ( (GNUNET_DNSPARSER_TYPE_A != record_type) &&
428          (GNUNET_DNSPARSER_TYPE_ALL != record_type) )
429       return GNUNET_NO;
430     payload = record->data.raw.data;
431     payload_len = record->data.raw.data_len;
432     break;
433   case GNUNET_DNSPARSER_TYPE_AAAA:
434     if ( (GNUNET_DNSPARSER_TYPE_AAAA != record_type) &&
435          (GNUNET_DNSPARSER_TYPE_ALL != record_type) )
436       return GNUNET_NO;
437     payload = record->data.raw.data;
438     payload_len = record->data.raw.data_len;
439     break;
440   default:
441     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
442                 "Cannot handle DNS response type %u: not supported here\n",
443                 record->type);
444     return GNUNET_NO;
445   }
446   env = GNUNET_MQ_msg_extra (msg,
447                              payload_len,
448                              GNUNET_MESSAGE_TYPE_RESOLVER_RESPONSE);
449   msg->id = request_id;
450   GNUNET_memcpy (&msg[1],
451                  payload,
452                  payload_len);
453   GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
454                   env);
455   return GNUNET_YES;
456 }
457
458
459 /**
460  * Send message to @a client that we transmitted all
461  * responses for @a request_id
462  *
463  * @param request_id to which request are we responding
464  * @param client where to send @a record
465  */
466 static void
467 send_end_msg (uint16_t request_id,
468               struct GNUNET_SERVICE_Client *client)
469 {
470   struct GNUNET_RESOLVER_ResponseMessage *msg;
471   struct GNUNET_MQ_Envelope *env;
472
473   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
474               "Sending END message\n");
475   env = GNUNET_MQ_msg (msg,
476                        GNUNET_MESSAGE_TYPE_RESOLVER_RESPONSE);
477   msg->id = request_id;
478   GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
479                   env);
480 }
481
482
483 /**
484  * Remove expired entries from @a rc
485  *
486  * @param rc entry in resolver cache
487  * @return #GNUNET_YES if @a rc was completely expired
488  *         #GNUNET_NO if some entries are left
489  */
490 static int
491 remove_expired (struct ResolveCache *rc)
492 {
493   struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
494   struct RecordListEntry *n;
495
496   for (struct RecordListEntry *pos = rc->records_head;
497        NULL != pos;
498        pos = n)
499   {
500     n = pos->next;
501     if (now.abs_value_us > pos->record->expiration_time.abs_value_us)
502       GNUNET_CONTAINER_DLL_remove (rc->records_head,
503                                    rc->records_tail,
504                                    pos);
505   }
506   if (NULL == rc->records_head)
507   {
508     free_cache_entry (rc);
509     return GNUNET_YES;
510   }
511   return GNUNET_NO;
512 }
513
514
515 /**
516  * Process DNS request for @a hostname with request ID @a request_id
517  * from @a client demanding records of type @a record_type.
518  *
519  * @param hostname DNS name to resolve
520  * @param record_type desired record type
521  * @param request_id client's request ID
522  * @param client who should get the result?
523  */
524 static void
525 process_get (const char *hostname,
526              uint16_t record_type,
527              uint16_t request_id,
528              struct GNUNET_SERVICE_Client *client);
529
530
531 /**
532  * Get an IP address as a string (works for both IPv4 and IPv6).  Note
533  * that the resolution happens asynchronously and that the first call
534  * may not immediately result in the FQN (but instead in a
535  * human-readable IP address).
536  *
537  * @param hostname what hostname was to be resolved
538  * @param record_type what type of record was requested
539  * @param request_id unique identification of the client's request
540  * @param client handle to the client making the request (for sending the reply)
541  */
542 static int
543 try_cache (const char *hostname,
544            uint16_t record_type,
545            uint16_t request_id,
546            struct GNUNET_SERVICE_Client *client)
547 {
548   struct ResolveCache *pos;
549   struct ResolveCache *next;
550   int found;
551
552   next = cache_head;
553   for (pos = next; NULL != pos; pos = next)
554   {
555     next = pos->next;
556     if (GNUNET_YES == remove_expired (pos))
557       continue;
558     if (0 == strcmp (pos->hostname,
559                      hostname))
560       break;
561   }
562   if (NULL == pos)
563   {
564     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
565                 "No cache entry for '%s'\n",
566                 hostname);
567     return GNUNET_NO;
568   }
569   if (cache_head != pos)
570   {
571     /* move result to head to achieve LRU for cache eviction */
572     GNUNET_CONTAINER_DLL_remove (cache_head,
573                                  cache_tail,
574                                  pos);
575     GNUNET_CONTAINER_DLL_insert (cache_head,
576                                  cache_tail,
577                                  pos);
578   }
579   found = GNUNET_NO;
580   for (struct RecordListEntry *rle = pos->records_head;
581        NULL != rle;
582        rle = rle->next)
583   {
584     const struct GNUNET_DNSPARSER_Record *record = rle->record;
585
586     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
587                 "Found cache entry for '%s', record type '%u'\n",
588                 hostname,
589                 record_type);
590     if ( (GNUNET_DNSPARSER_TYPE_CNAME == record->type) &&
591          (GNUNET_DNSPARSER_TYPE_CNAME != record_type) &&
592          (GNUNET_NO == found) )
593     {
594       const char *hostname = record->data.hostname;
595
596       process_get (hostname,
597                    record_type,
598                    request_id,
599                    client);
600       return GNUNET_YES; /* counts as a cache "hit" */
601     }
602     found |= send_reply (rle->record,
603                          record_type,
604                          request_id,
605                          client);
606   }
607   if (GNUNET_NO == found)
608     return GNUNET_NO; /* had records, but none matched! */
609   send_end_msg (request_id,
610                 client);
611   return GNUNET_YES;
612 }
613
614
615 /**
616  * Create DNS query for @a hostname of type @a type
617  * with DNS request ID @a dns_id.
618  *
619  * @param hostname DNS name to query
620  * @param type requested DNS record type
621  * @param dns_id what should be the DNS request ID
622  * @param packet_buf[out] where to write the request packet
623  * @param packet_size[out] set to size of @a packet_buf on success
624  * @return #GNUNET_OK on success
625  */
626 static int
627 pack (const char *hostname,
628       uint16_t type,
629       uint16_t dns_id,
630       char **packet_buf,
631       size_t *packet_size)
632 {
633   struct GNUNET_DNSPARSER_Query query;
634   struct GNUNET_DNSPARSER_Packet packet;
635
636   query.name = (char *)hostname;
637   query.type = type;
638   query.dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET;
639   memset (&packet,
640           0,
641           sizeof (packet));
642   packet.num_queries = 1;
643   packet.queries = &query;
644   packet.id = htons (dns_id);
645   packet.flags.recursion_desired = 1;
646   if (GNUNET_OK !=
647       GNUNET_DNSPARSER_pack (&packet,
648                              UINT16_MAX,
649                              packet_buf,
650                              packet_size))
651   {
652     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
653                 "Failed to pack query for hostname `%s'\n",
654                 hostname);
655     packet_buf = NULL;
656     return GNUNET_SYSERR;
657   }
658   return GNUNET_OK;
659 }
660
661
662 /**
663  * We got a result from DNS. Add it to the cache and
664  * see if we can make our client happy...
665  *
666  * @param cls the `struct ActiveLookup`
667  * @param dns the DNS response
668  * @param dns_len number of bytes in @a dns
669  */
670 static void
671 handle_resolve_result (void *cls,
672                        const struct GNUNET_TUN_DnsHeader *dns,
673                        size_t dns_len)
674 {
675   struct ActiveLookup *al = cls;
676   struct GNUNET_DNSPARSER_Packet *parsed;
677   struct ResolveCache *rc;
678
679   parsed = GNUNET_DNSPARSER_parse ((const char *)dns,
680                                    dns_len);
681   if (NULL == parsed)
682   {
683     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
684                 "Failed to parse DNS reply (hostname %s, request ID %u)\n",
685                 al->hostname,
686                 al->dns_id);
687     return;
688   }
689   if (al->dns_id != ntohs (parsed->id))
690   {
691     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
692                 "Request ID in DNS reply does not match\n");
693     GNUNET_DNSPARSER_free_packet (parsed);
694     return;
695   }
696   if (0 == parsed->num_answers)
697   {
698     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
699                 "DNS reply (hostname %s, request ID %u) contains no answers\n",
700                 al->hostname,
701                 al->request_id);
702     GNUNET_DNSPARSER_free_packet (parsed);
703     send_end_msg (al->request_id,
704                   al->client);
705     free_active_lookup (al);
706     return;
707   }
708   /* LRU-based cache eviction: we remove from tail */
709   while (cache_size > MAX_CACHE)
710     free_cache_entry (cache_tail);
711
712   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
713               "Got reply for hostname %s and request ID %u\n",
714               al->hostname,
715               al->request_id);
716   /* add to cache */
717   for (unsigned int i = 0; i != parsed->num_answers; i++)
718   {
719     struct GNUNET_DNSPARSER_Record *record = &parsed->answers[i];
720     struct RecordListEntry *rle;
721
722     for (rc = cache_head; NULL != rc; rc = rc->next)
723       if (0 == strcasecmp (rc->hostname,
724                            record->name))
725         break;
726     if (NULL == rc)
727     {
728       rc = GNUNET_new (struct ResolveCache);
729       rc->hostname = GNUNET_strdup (record->name);
730       GNUNET_CONTAINER_DLL_insert (cache_head,
731                                    cache_tail,
732                                    rc);
733       cache_size++;
734     }
735     /* TODO: ought to check first if we have this exact record
736        already in the cache! */
737     rle = GNUNET_new (struct RecordListEntry);
738     rle->record = GNUNET_DNSPARSER_duplicate_record (record);
739     GNUNET_CONTAINER_DLL_insert (rc->records_head,
740                                  rc->records_tail,
741                                  rle);
742   }
743   /* see if we need to do the 2nd request for AAAA records */
744   if ( (GNUNET_DNSPARSER_TYPE_ALL == al->record_type) &&
745        (GNUNET_NO == al->did_aaaa) )
746   {
747     char *packet_buf;
748     size_t packet_size;
749     uint16_t dns_id;
750
751     dns_id = (uint16_t) GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
752                                                   UINT16_MAX);
753     if (GNUNET_OK ==
754         pack (al->hostname,
755               GNUNET_DNSPARSER_TYPE_AAAA,
756               dns_id,
757               &packet_buf,
758               &packet_size))
759     {
760       al->did_aaaa = GNUNET_YES;
761       al->dns_id = dns_id;
762       GNUNET_DNSSTUB_resolve_cancel (al->resolve_handle);
763       al->resolve_handle =
764         GNUNET_DNSSTUB_resolve (dnsstub_ctx,
765                                 packet_buf,
766                                 packet_size,
767                                 &handle_resolve_result,
768                                 al);
769       return;
770     }
771   }
772
773   /* resume by trying again from cache */
774   if (GNUNET_NO ==
775       try_cache (al->hostname,
776                  al->record_type,
777                  al->request_id,
778                  al->client))
779     /* cache failed, tell client we could not get an answer */
780     send_end_msg (al->request_id,
781                   al->client);
782   free_active_lookup (al);
783   GNUNET_DNSPARSER_free_packet (parsed);
784 }
785
786
787 /**
788  * We encountered a timeout trying to perform a
789  * DNS lookup.
790  *
791  * @param cls a `struct ActiveLookup`
792  */
793 static void
794 handle_resolve_timeout (void *cls)
795 {
796   struct ActiveLookup *al = cls;
797
798   al->timeout_task = NULL;
799   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
800               "DNS lookup timeout!\n");
801   send_end_msg (al->request_id,
802                 al->client);
803   free_active_lookup (al);
804 }
805
806
807 /**
808  * Initiate an active lookup, then cache the result and
809  * try to then complete the resolution.
810  *
811  * @param hostname DNS name to resolve
812  * @param record_type record type to locate
813  * @param request_id client request ID
814  * @param client handle to the client
815  * @return #GNUNET_OK if the DNS query is now pending
816  */
817 static int
818 resolve_and_cache (const char* hostname,
819                    uint16_t record_type,
820                    uint16_t request_id,
821                    struct GNUNET_SERVICE_Client *client)
822 {
823   char *packet_buf;
824   size_t packet_size;
825   struct ActiveLookup *al;
826   uint16_t dns_id;
827   uint16_t type;
828
829   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
830               "resolve_and_cache\n");
831   dns_id = (uint16_t) GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
832                                                 UINT16_MAX);
833
834   if (GNUNET_DNSPARSER_TYPE_ALL == record_type)
835     type = GNUNET_DNSPARSER_TYPE_A;
836   else
837     type = record_type;
838   if (GNUNET_OK !=
839       pack (hostname,
840             type,
841             dns_id,
842             &packet_buf,
843             &packet_size))
844   {
845     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
846                 "Failed to pack query for hostname `%s'\n",
847                 hostname);
848     return GNUNET_SYSERR;
849   }
850
851   al = GNUNET_new (struct ActiveLookup);
852   al->hostname = GNUNET_strdup (hostname);
853   al->record_type = record_type;
854   al->request_id = request_id;
855   al->dns_id = dns_id;
856   al->client = client;
857   al->timeout_task = GNUNET_SCHEDULER_add_delayed (DNS_TIMEOUT,
858                                                    &handle_resolve_timeout,
859                                                    al);
860   al->resolve_handle =
861     GNUNET_DNSSTUB_resolve (dnsstub_ctx,
862                             packet_buf,
863                             packet_size,
864                             &handle_resolve_result,
865                             al);
866   GNUNET_free (packet_buf);
867   GNUNET_CONTAINER_DLL_insert (lookup_head,
868                                lookup_tail,
869                                al);
870   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
871               "Resolving %s, request_id = %u, dns_id = %u\n",
872               hostname,
873               (unsigned int) request_id,
874               (unsigned int) dns_id);
875   return GNUNET_OK;
876 }
877
878
879 /**
880  * Process DNS request for @a hostname with request ID @a request_id
881  * from @a client demanding records of type @a record_type.
882  *
883  * @param hostname DNS name to resolve
884  * @param record_type desired record type
885  * @param request_id client's request ID
886  * @param client who should get the result?
887  */
888 static void
889 process_get (const char *hostname,
890              uint16_t record_type,
891              uint16_t request_id,
892              struct GNUNET_SERVICE_Client *client)
893 {
894   if (GNUNET_NO ==
895       try_cache (hostname,
896                  record_type,
897                  request_id,
898                  client))
899   {
900     if (GNUNET_OK !=
901         resolve_and_cache (hostname,
902                            record_type,
903                            request_id,
904                            client))
905     {
906       send_end_msg (request_id,
907                     client);
908     }
909   }
910 }
911
912
913 /**
914  * Verify well-formedness of GET-message.
915  *
916  * @param cls closure, unused
917  * @param get the actual message
918  * @return #GNUNET_OK if @a get is well-formed
919  */
920 static int
921 check_get (void *cls,
922            const struct GNUNET_RESOLVER_GetMessage *get)
923 {
924   uint16_t size;
925   int direction;
926   int af;
927
928   (void) cls;
929   size = ntohs (get->header.size) - sizeof (*get);
930   direction = ntohl (get->direction);
931   if (GNUNET_NO == direction)
932   {
933     /* IP from hostname */
934     const char *hostname;
935
936     hostname = (const char *) &get[1];
937     if (hostname[size - 1] != '\0')
938     {
939       GNUNET_break (0);
940       return GNUNET_SYSERR;
941     }
942     return GNUNET_OK;
943   }
944   af = ntohl (get->af);
945   switch (af)
946   {
947   case AF_INET:
948     if (size != sizeof (struct in_addr))
949     {
950       GNUNET_break (0);
951       return GNUNET_SYSERR;
952     }
953     break;
954   case AF_INET6:
955     if (size != sizeof (struct in6_addr))
956     {
957       GNUNET_break (0);
958       return GNUNET_SYSERR;
959     }
960     break;
961   default:
962     GNUNET_break (0);
963     return GNUNET_SYSERR;
964   }
965   return GNUNET_OK;
966 }
967
968
969 /**
970  * Handle GET-message.
971  *
972  * @param cls identification of the client
973  * @param msg the actual message
974  */
975 static void
976 handle_get (void *cls,
977             const struct GNUNET_RESOLVER_GetMessage *msg)
978 {
979   struct GNUNET_SERVICE_Client *client = cls;
980   int direction;
981   int af;
982   uint16_t request_id;
983   char *hostname;
984
985   direction = ntohl (msg->direction);
986   af = ntohl (msg->af);
987   request_id = ntohs (msg->id);
988   if (GNUNET_NO == direction)
989   {
990     /* IP from hostname */
991     hostname = GNUNET_strdup ((const char *) &msg[1]);
992     switch (af)
993     {
994       case AF_UNSPEC:
995       {
996         process_get (hostname,
997                      GNUNET_DNSPARSER_TYPE_ALL,
998                      request_id,
999                      client);
1000         break;
1001       }
1002       case AF_INET:
1003       {
1004         process_get (hostname,
1005                      GNUNET_DNSPARSER_TYPE_A,
1006                      request_id,
1007                      client);
1008         break;
1009       }
1010       case AF_INET6:
1011       {
1012         process_get (hostname,
1013                      GNUNET_DNSPARSER_TYPE_AAAA,
1014                      request_id,
1015                      client);
1016         break;
1017       }
1018       default:
1019       {
1020         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1021                   "got invalid af: %d\n",
1022                   af);
1023         GNUNET_assert (0);
1024       }
1025     }
1026   }
1027   else
1028   {
1029     /* hostname from IP */
1030     hostname = make_reverse_hostname (&msg[1],
1031                                       af);
1032     process_get (hostname,
1033                  GNUNET_DNSPARSER_TYPE_PTR,
1034                  request_id,
1035                  client);
1036   }
1037   GNUNET_free_non_null (hostname);
1038   GNUNET_SERVICE_client_continue (client);
1039 }
1040
1041
1042 /**
1043  * Service is shutting down, clean up.
1044  *
1045  * @param cls NULL, unused
1046  */
1047 static void
1048 shutdown_task (void *cls)
1049 {
1050   (void) cls;
1051
1052   while (NULL != lookup_head)
1053     free_active_lookup (lookup_head);
1054   while (NULL != cache_head)
1055     free_cache_entry (cache_head);
1056   GNUNET_DNSSTUB_stop (dnsstub_ctx);
1057 }
1058
1059
1060 /**
1061  * Service is starting, initialize everything.
1062  *
1063  * @param cls NULL, unused
1064  * @param cfg our configuration
1065  * @param sh service handle
1066  */
1067 static void
1068 init_cb (void *cls,
1069          const struct GNUNET_CONFIGURATION_Handle *cfg,
1070          struct GNUNET_SERVICE_Handle *sh)
1071 {
1072   char **dns_servers;
1073   int num_dns_servers;
1074
1075   (void) cfg;
1076   (void) sh;
1077   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
1078                                  cls);
1079   dnsstub_ctx = GNUNET_DNSSTUB_start (128);
1080   num_dns_servers = lookup_dns_servers (&dns_servers);
1081   if (0 >= num_dns_servers)
1082   {
1083     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1084                 _("No DNS server available. DNS resolution will not be possible.\n"));
1085   }
1086   for (int i = 0; i < num_dns_servers; i++)
1087   {
1088     int result = GNUNET_DNSSTUB_add_dns_ip (dnsstub_ctx, dns_servers[i]);
1089     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1090                 "Adding DNS server '%s': %s\n",
1091                 dns_servers[i],
1092                 GNUNET_OK == result ? "success" : "failure");
1093     GNUNET_free (dns_servers[i]);
1094   }
1095   GNUNET_free_non_null (dns_servers);
1096 }
1097
1098
1099 /**
1100  * Callback called when a client connects to the service.
1101  *
1102  * @param cls closure for the service, unused
1103  * @param c the new client that connected to the service
1104  * @param mq the message queue used to send messages to the client
1105  * @return @a c
1106  */
1107 static void *
1108 connect_cb (void *cls,
1109             struct GNUNET_SERVICE_Client *c,
1110             struct GNUNET_MQ_Handle *mq)
1111 {
1112   (void) cls;
1113   (void) mq;
1114
1115   return c;
1116 }
1117
1118
1119 /**
1120  * Callback called when a client disconnected from the service
1121  *
1122  * @param cls closure for the service
1123  * @param c the client that disconnected
1124  * @param internal_cls should be equal to @a c
1125  */
1126 static void
1127 disconnect_cb (void *cls,
1128                struct GNUNET_SERVICE_Client *c,
1129                void *internal_cls)
1130 {
1131   struct ActiveLookup *n;
1132   (void) cls;
1133
1134   GNUNET_assert (c == internal_cls);
1135   n = lookup_head;
1136   for (struct ActiveLookup *al = n;
1137        NULL != al;
1138        al = n)
1139   {
1140     n = al->next;
1141     if (al->client == c)
1142       free_active_lookup (al);
1143   }
1144 }
1145
1146
1147 /**
1148  * Define "main" method using service macro.
1149  */
1150 GNUNET_SERVICE_MAIN
1151 ("resolver",
1152  GNUNET_SERVICE_OPTION_NONE,
1153  &init_cb,
1154  &connect_cb,
1155  &disconnect_cb,
1156  NULL,
1157  GNUNET_MQ_hd_var_size (get,
1158                         GNUNET_MESSAGE_TYPE_RESOLVER_REQUEST,
1159                         struct GNUNET_RESOLVER_GetMessage,
1160                         NULL),
1161  GNUNET_MQ_handler_end ());
1162
1163
1164 #if defined(LINUX) && defined(__GLIBC__)
1165 #include <malloc.h>
1166
1167 /**
1168  * MINIMIZE heap size (way below 128k) since this process doesn't need much.
1169  */
1170 void __attribute__ ((constructor))
1171 GNUNET_RESOLVER_memory_init ()
1172 {
1173   mallopt (M_TRIM_THRESHOLD, 4 * 1024);
1174   mallopt (M_TOP_PAD, 1 * 1024);
1175   malloc_trim (0);
1176 }
1177 #endif
1178
1179
1180 /* end of gnunet-service-resolver.c */