Merge branch 'master' of https://gnunet.org/git/gnunet
[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 struct Record
32 {
33   struct Record *next;
34
35   struct Record *prev;
36
37   struct GNUNET_DNSPARSER_Record *record;
38 };
39
40 /**
41  * A cached DNS lookup result.
42  */
43 struct ResolveCache
44 {
45   /**
46    * This is a doubly linked list.
47    */
48   struct ResolveCache *next;
49
50   /**
51    * This is a doubly linked list.
52    */
53   struct ResolveCache *prev;
54
55   /**
56    * type of queried DNS record
57    */
58   uint16_t record_type;
59
60   /**
61    * a pointer to the request_id if a query for this hostname/record_type
62    * is currently pending, NULL otherwise.
63    */
64   int16_t *request_id;
65
66   struct GNUNET_SERVICE_Client *client;
67
68   /**
69    * head of a double linked list containing the lookup results
70    */
71   struct Record *records_head;
72
73   /**
74    * tail of a double linked list containing the lookup results
75    */
76   struct Record *records_tail;
77
78   /**
79    * handle for cancelling a request
80    */
81   struct GNUNET_DNSSTUB_RequestSocket *resolve_handle;
82
83   /**
84    * handle for the resolution timeout task
85    */
86   struct GNUNET_SCHEDULER_Task *timeout_task;
87
88 };
89
90
91 /**
92  * Start of the linked list of cached DNS lookup results.
93  */
94 static struct ResolveCache *cache_head;
95
96 /**
97  * Tail of the linked list of cached DNS lookup results.
98  */
99 static struct ResolveCache *cache_tail;
100
101 /**
102  * context of dnsstub library
103  */
104 static struct GNUNET_DNSSTUB_Context *dnsstub_ctx;
105
106
107 void free_cache_entry (struct ResolveCache *entry)
108 {
109   struct Record *pos;
110   struct Record *next;
111  
112   next = entry->records_head;
113   while (NULL != (pos = next))
114   {
115     next = pos->next;
116     GNUNET_CONTAINER_DLL_remove (entry->records_head,
117                                  entry->records_tail,
118                                  pos);
119     if (NULL != pos->record)
120     {
121       GNUNET_DNSPARSER_free_record (pos->record);
122       GNUNET_free (pos->record);
123     }
124     GNUNET_free (pos);
125   }
126   if (NULL != entry->resolve_handle)
127   {
128     GNUNET_DNSSTUB_resolve_cancel (entry->resolve_handle);
129     entry->resolve_handle = NULL;
130   }
131   if (NULL != entry->timeout_task)
132   {
133     GNUNET_SCHEDULER_cancel (entry->timeout_task);
134     entry->timeout_task = NULL;
135   }
136   GNUNET_free_non_null (entry->request_id);
137   GNUNET_free (entry);
138 }
139
140
141 static char*
142 extract_dns_server (const char* line, size_t line_len)
143 {
144   if (0 == strncmp (line, "nameserver ", 11))
145     return GNUNET_strndup (line + 11, line_len - 11);
146   return NULL;
147 }
148
149  
150 /**
151  * reads the list of nameservers from /etc/resolve.conf
152  *
153  * @param server_addrs[out] a list of null-terminated server address strings
154  * @return the number of server addresses in @server_addrs, -1 on error
155  */
156 static ssize_t
157 lookup_dns_servers (char ***server_addrs)
158 {
159   struct GNUNET_DISK_FileHandle *fh;
160   char buf[2048];
161   ssize_t bytes_read;
162   size_t read_offset = 0;
163   unsigned int num_dns_servers = 0;
164     
165   fh = GNUNET_DISK_file_open ("/etc/resolv.conf",
166                               GNUNET_DISK_OPEN_READ,
167                               GNUNET_DISK_PERM_NONE);
168   if (NULL == fh)
169   {
170     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
171                 "Could not open /etc/resolv.conf. "
172                 "DNS resolution will not be possible.\n");
173     return -1;
174   }
175   bytes_read = GNUNET_DISK_file_read (fh,
176                                       buf,
177                                       sizeof (buf));
178   *server_addrs = NULL;
179   while (read_offset < bytes_read)
180   {
181     char *newline;
182     size_t line_len;
183     char *dns_server;
184     
185     newline = strchr (buf + read_offset, '\n');
186     if (NULL == newline)
187     {
188       break;
189     }
190     line_len = newline - buf - read_offset;
191     dns_server = extract_dns_server (buf + read_offset, line_len);
192     if (NULL != dns_server)
193     {
194       GNUNET_array_append (*server_addrs,
195                            num_dns_servers,
196                            dns_server);
197     }
198     read_offset += line_len + 1;
199   }
200   GNUNET_DISK_file_close (fh);
201   return num_dns_servers;
202 }
203
204
205 static char *
206 make_reverse_hostname (const void *ip, int af)
207 {
208   char *buf = GNUNET_new_array (80, char);
209   int pos = 0;
210   if (AF_INET == af)
211   {
212     struct in_addr *addr = (struct in_addr *)ip;
213     uint32_t ip_int = addr->s_addr;
214     for (int i = 3; i >= 0; i--)
215     {
216       int n = GNUNET_snprintf (buf + pos,
217                                80 - pos,
218                                "%u.",
219                                ((uint8_t *)&ip_int)[i]);
220       if (n < 0)
221       {
222         GNUNET_free (buf);
223         return NULL;
224       }
225       pos += n;
226     }
227     pos += GNUNET_snprintf (buf + pos, 80 - pos, "in-addr.arpa");
228   }
229   else if (AF_INET6 == af)
230   {
231     struct in6_addr *addr = (struct in6_addr *)ip;
232     for (int i = 15; i >= 0; i--)
233     {
234       int n = GNUNET_snprintf (buf + pos, 80 - pos, "%x.", addr->s6_addr[i] & 0xf);
235       if (n < 0)
236       {
237         GNUNET_free (buf);
238         return NULL;
239       }
240       pos += n;
241       n = GNUNET_snprintf (buf + pos, 80 - pos, "%x.", addr->s6_addr[i] >> 4);
242       if (n < 0)
243       {
244         GNUNET_free (buf);
245         return NULL;
246       }
247       pos += n;
248     }
249     pos += GNUNET_snprintf (buf + pos, 80 - pos, "ip6.arpa");
250   }
251   buf[pos] = '\0';
252   return buf;
253 }
254
255
256 static void
257 send_reply (struct GNUNET_DNSPARSER_Record *record,
258             uint16_t request_id,
259             struct GNUNET_SERVICE_Client *client)
260 {
261   struct GNUNET_RESOLVER_ResponseMessage *msg;
262   struct GNUNET_MQ_Envelope *env;
263   void *payload;
264   size_t payload_len;
265
266   switch (record->type)
267   {
268     case GNUNET_DNSPARSER_TYPE_PTR:
269     {
270       char *hostname = record->data.hostname;
271       payload = hostname;
272       payload_len = strlen (hostname) + 1;
273       break;
274     }
275     case GNUNET_DNSPARSER_TYPE_A:
276     case GNUNET_DNSPARSER_TYPE_AAAA:
277     {
278       payload = record->data.raw.data;
279       payload_len = record->data.raw.data_len;
280       break;         
281     }
282     default:
283     {
284       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
285                   "Cannot handle DNS response type: unimplemented\n");
286       return;
287     }
288   }
289   env = GNUNET_MQ_msg_extra (msg,
290                              payload_len,
291                              GNUNET_MESSAGE_TYPE_RESOLVER_RESPONSE);
292   msg->id = request_id;
293   GNUNET_memcpy (&msg[1],
294                  payload,
295                  payload_len);
296   GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
297                   env);
298 }
299
300
301 static void
302 send_end_msg (uint16_t request_id,
303               struct GNUNET_SERVICE_Client *client)
304 {
305   struct GNUNET_RESOLVER_ResponseMessage *msg;
306   struct GNUNET_MQ_Envelope *env;
307
308   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
309               "Sending end message\n");
310   env = GNUNET_MQ_msg (msg,
311                        GNUNET_MESSAGE_TYPE_RESOLVER_RESPONSE);
312   msg->id = request_id;
313   GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
314                   env);
315 }
316
317
318 static void
319 handle_resolve_result (void *cls,
320                        const struct GNUNET_TUN_DnsHeader *dns,
321                        size_t dns_len)
322 {
323   struct ResolveCache *cache = cls;
324   struct GNUNET_DNSPARSER_Packet *parsed;
325   uint16_t request_id = *cache->request_id;
326   struct GNUNET_SERVICE_Client *client = cache->client;
327
328   parsed = GNUNET_DNSPARSER_parse ((const char *)dns,
329                                    dns_len);
330   if (NULL == parsed)
331   {
332     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
333                 "Failed to parse DNS reply (request ID %u\n",
334                 request_id);
335     return;
336   }
337   if (request_id != ntohs (parsed->id))
338   {
339     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
340                 "Request ID in DNS reply does not match\n");
341     return;
342   }
343   else if (0 == parsed->num_answers)
344   {
345     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
346                 "DNS reply (request ID %u) contains no answers\n",
347                 request_id);
348     GNUNET_CONTAINER_DLL_remove (cache_head,
349                                  cache_tail,
350                                  cache);
351     free_cache_entry (cache);
352     cache = NULL;
353   }
354   else
355   {
356     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
357                 "Got reply for request ID %u\n",
358                 request_id);
359     for (unsigned int i = 0; i != parsed->num_answers; i++)
360     {
361       struct Record *cache_entry = GNUNET_new (struct Record);
362       struct GNUNET_DNSPARSER_Record *record = &parsed->answers[i];
363       cache_entry->record = GNUNET_DNSPARSER_duplicate_record (record);
364       GNUNET_CONTAINER_DLL_insert (cache->records_head,
365                                    cache->records_tail,
366                                    cache_entry);
367       send_reply (cache_entry->record,
368                   request_id,
369                   cache->client);
370     }
371     GNUNET_free_non_null (cache->request_id);
372     cache->request_id = NULL;
373   }
374   send_end_msg (request_id,
375                 client);
376   if (NULL != cache)
377     cache->client = NULL;
378   GNUNET_SCHEDULER_cancel (cache->timeout_task);
379   GNUNET_DNSSTUB_resolve_cancel (cache->resolve_handle);
380   cache->timeout_task = NULL;
381   cache->resolve_handle = NULL;
382   GNUNET_DNSPARSER_free_packet (parsed);
383 }
384
385
386 static void
387 handle_resolve_timeout (void *cls)
388 {
389   struct ResolveCache *cache = cls;
390
391   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 
392               "timeout!\n");
393   if (NULL != cache->resolve_handle)
394   {
395     GNUNET_DNSSTUB_resolve_cancel (cache->resolve_handle);
396     cache->resolve_handle = NULL;
397   }
398   GNUNET_CONTAINER_DLL_remove (cache_head,
399                                cache_tail,
400                                cache);
401   free_cache_entry (cache);
402 }
403
404
405 static int
406 resolve_and_cache (const char* hostname,
407                    uint16_t record_type,
408                    uint16_t request_id,
409                    struct GNUNET_SERVICE_Client *client)
410 {
411   char *packet_buf;
412   size_t packet_size;
413   struct GNUNET_DNSPARSER_Query query;
414   struct GNUNET_DNSPARSER_Packet packet;
415   struct ResolveCache *cache;
416   struct GNUNET_TIME_Relative timeout =
417     GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5);
418
419   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
420               "resolve_and_cache\n");
421   query.name = (char *)hostname;
422   query.type = record_type;
423   query.dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET;
424   memset (&packet,
425           0,
426           sizeof (packet));
427   packet.num_queries = 1;
428   packet.queries = &query;
429   packet.id = htons (request_id);
430   packet.flags.recursion_desired = 1;
431   if (GNUNET_OK != 
432       GNUNET_DNSPARSER_pack (&packet,
433                              UINT16_MAX,
434                              &packet_buf,
435                              &packet_size))
436   {
437     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
438                 "Failed to pack query for hostname `%s'\n",
439                 hostname);
440     return GNUNET_SYSERR;
441  
442   }
443   cache = GNUNET_malloc (sizeof (struct ResolveCache));
444   cache->record_type = record_type;
445   cache->request_id = GNUNET_memdup (&request_id, sizeof (request_id));
446   cache->client = client;
447   cache->timeout_task = GNUNET_SCHEDULER_add_delayed (timeout,
448                                                       &handle_resolve_timeout,
449                                                       cache);
450   cache->resolve_handle = 
451     GNUNET_DNSSTUB_resolve (dnsstub_ctx,
452                             packet_buf,
453                             packet_size,
454                             &handle_resolve_result,
455                             cache);
456   GNUNET_CONTAINER_DLL_insert (cache_head,
457                                cache_tail,
458                                cache);
459   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
460               "resolve %s, request_id = %u\n",
461               hostname,
462               request_id);
463   GNUNET_free (packet_buf);
464   return GNUNET_OK;
465 }
466
467
468 static const char *
469 get_hostname (struct ResolveCache *cache_entry)
470 {
471   if (NULL != cache_entry->records_head)
472   {
473     GNUNET_assert (NULL != cache_entry->records_head);
474     GNUNET_assert (NULL != cache_entry->records_head->record);
475     GNUNET_assert (NULL != cache_entry->records_head->record->name);
476     return cache_entry->records_head->record->name;
477   }
478   return NULL;
479 }
480
481
482 static const uint16_t *
483 get_record_type (struct ResolveCache *cache_entry)
484 {
485   if (NULL != cache_entry->records_head)
486     return &cache_entry->record_type;
487   return NULL; 
488 }
489
490
491 static const struct GNUNET_TIME_Absolute *
492 get_expiration_time (struct ResolveCache *cache_entry)
493 {
494   if (NULL != cache_entry->records_head)
495     return &cache_entry->records_head->record->expiration_time;
496   return NULL;
497 }
498
499
500 static int
501 remove_if_expired (struct ResolveCache *cache_entry)
502 {
503   struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
504
505   if ( (NULL != cache_entry->records_head) &&
506        (now.abs_value_us > get_expiration_time (cache_entry)->abs_value_us) )
507   {
508     GNUNET_CONTAINER_DLL_remove (cache_head,
509                                  cache_tail,
510                                  cache_entry);
511     free_cache_entry (cache_entry);
512     return GNUNET_YES;
513   }
514   return GNUNET_NO;
515 }
516
517
518 /**
519  * Get an IP address as a string (works for both IPv4 and IPv6).  Note
520  * that the resolution happens asynchronously and that the first call
521  * may not immediately result in the FQN (but instead in a
522  * human-readable IP address).
523  *
524  * @param client handle to the client making the request (for sending the reply)
525  * @param af AF_INET or AF_INET6
526  * @param ip `struct in_addr` or `struct in6_addr`
527  */
528 static int
529 try_cache (const char *hostname,
530            uint16_t record_type,
531            uint16_t request_id,
532            struct GNUNET_SERVICE_Client *client)
533 {
534   struct ResolveCache *pos;
535   struct ResolveCache *next;
536
537   next = cache_head;
538   while ( (NULL != (pos = next)) &&
539           ( (NULL == pos->records_head) ||
540             (0 != strcmp (get_hostname (pos), hostname)) ||
541             (*get_record_type (pos) != record_type) ) )
542   {
543     next = pos->next;
544     remove_if_expired (pos);
545   }
546   if (NULL != pos)
547   {
548     if (GNUNET_NO == remove_if_expired (pos))
549     {
550       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
551                   "found cache entry for '%s', record type '%u'\n",
552                   hostname,
553                   record_type);
554       struct Record *cache_pos = pos->records_head;
555       while (NULL != cache_pos)
556       {
557         send_reply (cache_pos->record,
558                     request_id,
559                     client);
560         cache_pos = cache_pos->next;
561       }
562       send_end_msg (request_id,
563                     client);
564       return GNUNET_YES;
565     }
566   }
567   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
568               "no cache entry for '%s'\n",
569               hostname);
570   return GNUNET_NO;
571 }
572
573
574 /**
575  * Verify well-formedness of GET-message.
576  *
577  * @param cls closure, unused
578  * @param get the actual message
579  * @return #GNUNET_OK if @a get is well-formed
580  */
581 static int
582 check_get (void *cls,
583            const struct GNUNET_RESOLVER_GetMessage *get)
584 {
585   uint16_t size;
586   int direction;
587   int af;
588
589   (void) cls;
590   size = ntohs (get->header.size) - sizeof (*get);
591   direction = ntohl (get->direction);
592   if (GNUNET_NO == direction)
593   {
594     /* IP from hostname */
595     const char *hostname;
596
597     hostname = (const char *) &get[1];
598     if (hostname[size - 1] != '\0')
599     {
600       GNUNET_break (0);
601       return GNUNET_SYSERR;
602     }
603     return GNUNET_OK;
604   }
605   af = ntohl (get->af);
606   switch (af)
607   {
608   case AF_INET:
609     if (size != sizeof (struct in_addr))
610     {
611       GNUNET_break (0);
612       return GNUNET_SYSERR;
613     }
614     break;
615   case AF_INET6:
616     if (size != sizeof (struct in6_addr))
617     {
618       GNUNET_break (0);
619       return GNUNET_SYSERR;
620     }
621     break;
622   default:
623     GNUNET_break (0);
624     return GNUNET_SYSERR;
625   }
626   return GNUNET_OK;
627 }
628
629
630 static void
631 process_get (const char *hostname,
632              uint16_t record_type,
633              uint16_t request_id,
634              struct GNUNET_SERVICE_Client *client)
635 {
636   if (GNUNET_NO == try_cache (hostname, record_type, request_id, client))
637   {
638     int result = resolve_and_cache (hostname,
639                                     record_type,
640                                     request_id,
641                                     client);
642     GNUNET_assert (GNUNET_OK == result);
643   }
644 }
645
646
647 /**
648  * Handle GET-message.
649  *
650  * @param cls identification of the client
651  * @param msg the actual message
652  */
653 static void
654 handle_get (void *cls,
655             const struct GNUNET_RESOLVER_GetMessage *msg)
656 {
657   struct GNUNET_SERVICE_Client *client = cls;
658   int direction;
659   int af;
660   uint16_t request_id;
661   const char *hostname;
662
663   direction = ntohl (msg->direction);
664   af = ntohl (msg->af);
665   request_id = ntohs (msg->id);
666   if (GNUNET_NO == direction)
667   {
668     /* IP from hostname */
669     hostname = GNUNET_strdup ((const char *) &msg[1]);
670     switch (af)
671     {
672       case AF_UNSPEC:
673       {
674         process_get (hostname, GNUNET_DNSPARSER_TYPE_ALL, request_id, client);
675         break;
676       }
677       case AF_INET:
678       {
679         process_get (hostname, GNUNET_DNSPARSER_TYPE_A, request_id, client);
680         break;
681       }
682       case AF_INET6:
683       {
684         process_get (hostname, GNUNET_DNSPARSER_TYPE_AAAA, request_id, client);
685         break;
686       }
687       default:
688       {
689         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
690                   "got invalid af: %d\n",
691                   af);
692         GNUNET_assert (0);
693       }
694     }
695   }
696   else
697   {
698     /* hostname from IP */
699     hostname = make_reverse_hostname (&msg[1], af); 
700     process_get (hostname, GNUNET_DNSPARSER_TYPE_PTR, request_id, client);
701   }
702   GNUNET_free_non_null ((char *)hostname);
703   GNUNET_SERVICE_client_continue (client);
704 }
705
706
707 static void 
708 shutdown_task (void *cls)
709 {
710   (void) cls;
711   struct ResolveCache *pos;
712
713   while (NULL != (pos = cache_head))
714   {
715     GNUNET_CONTAINER_DLL_remove (cache_head,
716                                  cache_tail,
717                                  pos);
718     free_cache_entry (pos);
719   }
720   GNUNET_DNSSTUB_stop (dnsstub_ctx);
721 }
722
723
724 static void
725 init_cb (void *cls,
726          const struct GNUNET_CONFIGURATION_Handle *cfg,
727          struct GNUNET_SERVICE_Handle *sh)
728 {
729   (void) cfg;
730   (void) sh;
731
732   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
733                                  cls);
734   dnsstub_ctx = GNUNET_DNSSTUB_start (128);
735   char **dns_servers;
736   ssize_t num_dns_servers = lookup_dns_servers (&dns_servers);
737   if (0 == num_dns_servers)
738   {
739     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
740                 "no DNS server available. DNS resolution will not be possible.\n");
741   }
742   for (int i = 0; i != num_dns_servers; i++)
743   {
744     int result = GNUNET_DNSSTUB_add_dns_ip (dnsstub_ctx, dns_servers[i]);
745     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
746                 "Adding DNS server '%s': %s\n",
747                 dns_servers[i],
748                 GNUNET_OK == result ? "success" : "failure");
749     GNUNET_free (dns_servers[i]);
750   }
751   GNUNET_free_non_null (dns_servers);
752 }
753
754
755 /**
756  * Callback called when a client connects to the service.
757  *
758  * @param cls closure for the service, unused
759  * @param c the new client that connected to the service
760  * @param mq the message queue used to send messages to the client
761  * @return @a c
762  */
763 static void *
764 connect_cb (void *cls,
765             struct GNUNET_SERVICE_Client *c,
766             struct GNUNET_MQ_Handle *mq)
767 {
768   (void) cls;
769   (void) mq;
770
771   return c;
772 }
773
774
775 /**
776  * Callback called when a client disconnected from the service
777  *
778  * @param cls closure for the service
779  * @param c the client that disconnected
780  * @param internal_cls should be equal to @a c
781  */
782 static void
783 disconnect_cb (void *cls,
784                struct GNUNET_SERVICE_Client *c,
785                void *internal_cls)
786 {
787   (void) cls;
788   struct ResolveCache *pos = cache_head; 
789
790   while (NULL != pos)
791   {
792     if (pos->client == c)
793     {
794       pos->client = NULL;
795     }
796     pos = pos->next;
797   }
798   GNUNET_assert (c == internal_cls);
799 }
800
801
802 /**
803  * Define "main" method using service macro.
804  */
805 GNUNET_SERVICE_MAIN
806 ("resolver",
807  GNUNET_SERVICE_OPTION_NONE,
808  &init_cb,
809  &connect_cb,
810  &disconnect_cb,
811  NULL,
812  GNUNET_MQ_hd_var_size (get,
813                         GNUNET_MESSAGE_TYPE_RESOLVER_REQUEST,
814                         struct GNUNET_RESOLVER_GetMessage,
815                         NULL),
816  GNUNET_MQ_handler_end ());
817
818
819 #if defined(LINUX) && defined(__GLIBC__)
820 #include <malloc.h>
821
822 /**
823  * MINIMIZE heap size (way below 128k) since this process doesn't need much.
824  */
825 void __attribute__ ((constructor))
826 GNUNET_RESOLVER_memory_init ()
827 {
828   mallopt (M_TRIM_THRESHOLD, 4 * 1024);
829   mallopt (M_TOP_PAD, 1 * 1024);
830   malloc_trim (0);
831 }
832 #endif
833
834
835 /* end of gnunet-service-resolver.c */