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